package com.arpa.wms.service;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.jpush.api.report.UsersResult;
import com.alibaba.fastjson.JSONObject;
import com.arpa.core.web.Result;
import com.arpa.ntocc.common.common.exception.ServiceException;
import com.arpa.ntocc.common.common.util.UserUtil;
import com.arpa.oms.service.IOmsOutboundService;
import com.arpa.wms.cache.WarehouseCache;
import com.arpa.wms.common.util.ArpaCloudUtil;
import com.arpa.wms.domain.consts.*;
import com.arpa.wms.domain.dto.InventoryOperationDto;
import com.arpa.wms.domain.entity.*;
import com.arpa.wms.domain.enums.*;
import com.arpa.wms.mapper.CloudprintStandardTemplatesMapper;
import com.arpa.wms.mapper.OutboundMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Maps;
import com.qimen.api.DefaultQimenClient;
import com.qimen.api.QimenClient;
import com.qimen.api.request.DeliveryorderConfirmRequest;
import com.qimen.api.response.DeliveryorderConfirmResponse;
import com.taobao.api.ApiException;
import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

import static com.arpa.wms.domain.consts.NumConst.NUM_ONE;
import static com.arpa.wms.domain.consts.NumConst.NUM_ZERO;
import static java.util.stream.Collectors.toList;

/**
 * 出库确认Service
 */
@Service
@Log4j2
public class OutCheckService {


    private static final String GET_WAY_BILL_HAS_EXCEPTION_UPDATE_DELIVERY_STATUS_UN_DELIVERD = "获取面单异常【{}】， errorCode:{},errorMessage:{}";

    @Value("${taobao.qimen.url}")
    private String url;
    @Value("${taobao.qimen.appkey}")
    private String appkey;
    @Value("${taobao.qimen.secret}")
    private String secret;

    @Autowired
    private ArpaCloudUtil arpaCloudUtil;

    /**
     * 测试环境请求url
     */
    //@Value("${taobao.wly.dailyUrl}")
    //private String dailyUrl;

    /**
     * 测试环境appKey
     */
    //@Value("${taobao.wly.dailyAppKey}")
    //private String dailyAppKey;
    /**
     * 测试环境secretKey
     */
    //@Value("${taobao.wly.dailySecretKey}")
    //private String dailySecretKey;
    /**
     * 测试环境token，token是商家在物流云平台授权ISV后生成的授权码，目前需要商家配置在ISV的软件中
     */
    //@Value("${taobao.wly.dailyToken}")
    private String dailyToken;
    @Autowired
    private IDeliveryService iDeliveryService;
    @Autowired
    private IDeliveryItemService iDeliveryItemService;
    @Autowired
    private IDeliveryDistributionService iDeliveryDistributionService;
    @Autowired
    private IInventoryService iInventoryService;
    @Autowired
    private IContainerService iContainerService;
    @Autowired
    private IOutboundItemService iOutboundItemService;
    @Autowired
    private IOutboundRegisterService iOutboundRegisterService;
    @Autowired
    private IOutboundService iOutboundService;
    @Resource
    private OutboundMapper outboundMapper;
    @Resource
    private CloudprintStandardTemplatesMapper cloudprintStandardTemplatesMapper;

    @Autowired
    private IInventoryRecordService iInventoryRecordService;

    @Autowired
    private ConfirmDeliveryUpdateDeliveryStatusService confirmDeliveryUpdateDeliveryStatusService;

    @Autowired
    private TmsWaybillUpdateService tmsWaybillUpdateService;

    @Autowired
    private InventoryOperationService inventoryOperationService;
    @Autowired
    private ILocationService iLocationService;

    @Autowired
    private WarehouseCache warehouseCache;

    @Autowired
    @Lazy
    private IPickingService pickingService;

    @Autowired
    private IOmsOutboundService omsOutboundService;

    /**
     * 出库确认
     * 已作废不在使用
     *
     * @param outbound
     * @return
     */
    /*@Transactional(rollbackFor = {Exception.class})
    @Deprecated
    public Result outCheck(Outbound outbound, boolean isReview) {
        log.info("出库单确认前，处理作废订单");
        //处理作废单
        handleInvalid(outbound);
        if (OutboundStatusEnum.CANCEL_OUT.getValue().equals(outbound.getStatus())) {

            return Result.error("订单已作废，出库单取消出库");
        }

        // 根据出库单波次号获取所有未废除、数量符合的订单号
        List<String> codeList = iDeliveryService.selectCodeForOutCheck(outbound.getWavePickingCode());

        log.info("根据出库单波次号【{}】获取所有未废除、数量符合的订单号【{}】", outbound.getWavePickingCode(), codeList);
        //订单是从wms创建的话，走原来的出库逻辑（仅限按单拣货的情况）
        if (codeList.size() == NUM_ONE) {
            Delivery delivery = iDeliveryService.getOne(new QueryWrapper<Delivery>().lambda().eq(Delivery::getCode, codeList.get(NUM_ZERO)));
            if ("wms".equals(delivery.getSourceType())) {
                return iOutboundService.outCheck(outbound);
            }
        }
        //将查出来所有得订单号，按10条分组
        List<List<String>> codes = Lists.partition(codeList, 10);

        // 线程结果集
        Map<Integer, Future<Boolean>> futureMap = Maps.newHashMap();

        for (List<String> deliveryCodes : codes) {
            futureMap.put(deliveryCodes.hashCode(), outCheckByDeliveryCode(outbound, deliveryCodes));
        }

        List<Integer> failCodes = Lists.newArrayList();
        StringBuilder failMsg = new StringBuilder();
        futureMap.forEach((key, future) -> {
            try {
                while (true) {
                    if (future.isDone()) {
                        Boolean status = future.get();
                        if (!status) {
                            failCodes.add(key);
                        }
                        break;
                    }
                }
            } catch (ServiceException | InterruptedException | ExecutionException e) {
                failMsg.append("订单:").append(key).append("异常信息:").append(e.getMessage()).append(";");
                failCodes.add(key);
                Thread.currentThread().interrupt();
            }
        });

        log.info("更新出库单状态前，处理作废订单");
        //处理作废单
        handleInvalid(outbound);

        // 释放容器
        List<String> containerCodes = iDeliveryDistributionService.getContainerCodesForOutCheck(outbound.getWavePickingCode());
        iContainerService.batchReleaseContainerForEmptyInventory(containerCodes);

        if (OutboundStatusEnum.CANCEL_OUT.getValue().equals(outbound.getStatus())) {

            return Result.error("订单已作废，出库单取消出库");
        }
        // 更新出库单明细出库数量,
//        iOutboundItemService.updateOutQuantityByDistribution(outbound.getCode(), outbound.getWavePickingCode());
        // 更新出库单数量，现在暂时用订单出库数量更新出库单数量，暂时不能用出库明细更新出库单数量，因为无法关联相同商品的出库明细跟分配单的关系
//        this.updateOutQuantityByItems(outbound.getCode());
        outboundMapper.updateOutQuantityByDelivery(outbound.getCode());
        //  如果出库数量！=计划数量 则状态为“部分出库”，否则“出库完成” ，使用两个方法， 主要是sql略复杂

        //更新临时库存（扣除）  outCheckByDeliveryCode这个方法里面已经扣除库存了
//        updateTemInventory(outbound,isReview);
        //获取波次号下未取消的订单数量
        int count = iDeliveryService.count(new LambdaQueryWrapper<Delivery>()
                .eq(Delivery::getWavePickingCode, outbound.getWavePickingCode())
                .eq(Delivery::getIsCancel, NUM_ONE));
        if (count > 0) {
            outboundMapper.updateStatusForOutCheck(outbound.getCode(),
                    OutboundStatusEnum.NOT_OUT.getValue(),
                    OutboundStatusEnum.PAET_OUT.getValue(),
                    OutboundStatusEnum.FINISH_OUT.getValue());

        } else {
            outboundMapper.updateStatusForOutCheck(outbound.getCode(), OutboundStatusEnum.NOT_OUT.getValue(), OutboundStatusEnum.PAET_OUT.getValue(), OutboundStatusEnum.FINISH_OUT.getValue());

        }


        return Result.ok();
    }
*/
    @Transactional(rollbackFor = Exception.class)
    public void handleInvalid(Outbound outbound) {
        if (StrUtil.isBlank(outbound.getTemporaryCode())) {
            //按单出库处理作废单
            handleSingleInvalid(outbound);
        } else {
            //批量出库处理作废单
            //handleBatchInvalid(outbound);

            handleDeliveryCancel(outbound);
        }
    }

    /**
     * 更新临时库存
     *
     * @param outbound
     * @param isReview 是否跳过复核，true 跳过，false 不跳过
     */
    private void updateTemInventory(Outbound outbound, boolean isReview) {
        log.info("出库确认更新临时库存，是否跳过复核：{}出库单信息:{}", isReview, JSONUtil.toJsonStr(outbound));
        if (isReview) {
            String wavePickingCode = outbound.getWavePickingCode();
            List<DeliveryDistribution> deliveryDistributions = this.iDeliveryDistributionService.list(new LambdaQueryWrapper<DeliveryDistribution>().eq(DeliveryDistribution::getWavePickingCode, wavePickingCode));
            log.info("出库确认更新临时库存，是否跳过复核：{} 出库单所有订单分配信息:{}", isReview, JSONUtil.toJsonStr(deliveryDistributions));
            deliveryDistributions.forEach(r -> {
                //跳过复核的订单 拣货数量代替复核数量
                //iInventoryService.adjustQuantity(r.getInventoryCode(), r.getPickingQuantity().negate());
                iInventoryService.reduceQuantity(r.getInventoryCode(), r.getRecheckQuantity());

                Inventory tempInventory = iInventoryService.getOne(new QueryWrapper<Inventory>().lambda().eq(Inventory::getCode, r.getInventoryCode()));
                iInventoryRecordService.insertRecord(tempInventory, outbound.getCode(), r.getPickingQuantity(), InventoryRecordTypeEnum.OUTBOUND.getValue());
            });
        } else {
            String wavePickingCode = outbound.getWavePickingCode();
            List<DeliveryDistribution> deliveryDistributions = this.iDeliveryDistributionService.list(new LambdaQueryWrapper<DeliveryDistribution>().eq(DeliveryDistribution::getWavePickingCode, wavePickingCode));
            log.info("出库确认更新临时库存，是否跳过复核：{} 出库单所有订单分配信息:{}", isReview, JSONUtil.toJsonStr(deliveryDistributions));
            deliveryDistributions.forEach(r -> {
                //跳过复核的订单 拣货数量代替复核数量
                //iInventoryService.adjustQuantity(r.getInventoryCode(), r.getRecheckQuantity().negate());
                iInventoryService.reduceQuantity(r.getInventoryCode(), r.getRecheckQuantity());

                Inventory tempInventory = iInventoryService.getOne(new QueryWrapper<Inventory>().lambda().eq(Inventory::getCode, r.getInventoryCode()));
                iInventoryRecordService.insertRecord(tempInventory, outbound.getCode(), r.getRecheckQuantity(), InventoryRecordTypeEnum.OUTBOUND.getValue());
            });
        }


      /*  //
        List<OutboundItem> outboundItems = iOutboundItemService.list(new QueryWrapper<OutboundItem>().lambda()
                .eq(OutboundItem::getOutboundCode, outbound.getCode()).eq(OutboundItem::getDeleted, 0));
        for(OutboundItem item: outboundItems){



            // 更新临时库存（扣除）
            String inventoryCode = item.getInventoryCode();
            BigDecimal changeQuantity = item.getRecheckQuantity().negate();
            iInventoryService.adjustQuantity(inventoryCode, changeQuantity);

            // 记录库存变动日志
            Inventory tempInventory = iInventoryService.getOne(new QueryWrapper<Inventory>().lambda().eq(Inventory::getCode, inventoryCode));
            iInventoryRecordService.insertRecord(tempInventory, item.getOutboundCode(), changeQuantity, InventoryRecordTypeEnum.OUTBOUND.getValue());
        }*/
    }


    /**
     * 按单出库，订单取消作废，更新出库单为取消出库
     *
     * @param outbound 出库单
     */
    private void handleSingleInvalid(Outbound outbound) {
        String groupCode = outbound.getGroupCode();
        log.info("按单出库确认，处理取消的订单");
        Delivery delivery = iDeliveryService.getOne(new QueryWrapper<Delivery>().lambda()
                .eq(Delivery::getCode, outbound.getDeliveryCode())
                .eq(Delivery::getGroupCode,groupCode)
                .ne(Delivery::getStatus, DeliveryStatusEnum.VOID.getValue())
                .eq(Delivery::getDeleted, NumConst.NUM_ZERO).eq(Delivery::getIsCancel, NUM_ONE));
        if (ObjectUtil.isNotNull(delivery)) {
            //释放库存到退拣库位
            List<OutboundItem> outboundItems = iOutboundItemService.list(new QueryWrapper<OutboundItem>().lambda()
                    .eq(OutboundItem::getOutboundCode, outbound.getCode())
                    .eq(OutboundItem::getGroupCode,groupCode)
                    .eq(OutboundItem::getDeleted, NumConst.NUM_ZERO));
            for (OutboundItem item : outboundItems) {
                iInventoryService.stockoutInventory(item.getInventoryCode(), item.getPlanQuantity(), StockoutTypeEnum.OUT_CHECK.getValue(), item.getOutboundCode());
            }
            //含有作废单时，将出库单、出库明细、出库确认的拣货数量、退拣数量、复核数量置零，设置状态为取消出库
            iOutboundService.update(new UpdateWrapper<Outbound>().lambda()
                    .eq(Outbound::getCode, outbound.getCode())
                    .eq(Outbound::getGroupCode,groupCode)
                    .set(Outbound::getPlanQuantity, BigDecimal.ZERO)
                    .set(Outbound::getRecheckQuantity, BigDecimal.ZERO)
                    .set(Outbound::getReturnQuantity, BigDecimal.ZERO)
                    .set(Outbound::getStatus, OutboundStatusEnum.CANCEL_OUT.getValue())

            );
            iOutboundItemService.update(new UpdateWrapper<OutboundItem>().lambda()
                    .eq(OutboundItem::getOutboundCode, outbound.getCode())
                    .eq(OutboundItem::getGroupCode,groupCode)
                    .set(OutboundItem::getPlanQuantity, BigDecimal.ZERO).set(OutboundItem::getRecheckQuantity, BigDecimal.ZERO)
                    .set(OutboundItem::getRecheckUnitQuantity, BigDecimal.ZERO).set(OutboundItem::getReturnQuantity, BigDecimal.ZERO));
            iOutboundRegisterService.update(new UpdateWrapper<OutboundRegister>().lambda()
                    .eq(OutboundRegister::getOutboundCode, outbound.getCode())
                    .eq(OutboundRegister::getGroupCode, groupCode)
                    .set(OutboundRegister::getPlanQuantity, BigDecimal.ZERO).set(OutboundRegister::getRecheckQuantity, BigDecimal.ZERO)
                    .set(OutboundRegister::getNotRecheckQuantity, BigDecimal.ZERO));
            //设置出库单状态为取消出库
            outbound.setStatus(OutboundStatusEnum.CANCEL_OUT.getValue());
            // 更新订单的状态，改为作废
            iDeliveryService.update(new UpdateWrapper<Delivery>().lambda()
                    .eq(Delivery::getCode, outbound.getDeliveryCode())
                    .eq(Delivery::getGroupCode,groupCode)
                    .ne(Delivery::getStatus, DeliveryStatusEnum.VOID.getValue())
                    .eq(Delivery::getDeleted, NumConst.NUM_ZERO)
                    .eq(Delivery::getIsCancel, NUM_ONE)
                    .set(Delivery::getStatus, DeliveryStatusEnum.VOID.getValue()));
        }
    }

    /**
     * 批量出库处理订单取消
     *
     * @param outbound
     */
    private void handleBatchInvalid(Outbound outbound) {
        log.info("波次出库确认，处理取消订单");
        List<Delivery> deliveryList = iDeliveryService.list(new QueryWrapper<Delivery>().lambda()
                .eq(Delivery::getWavePickingCode, outbound.getWavePickingCode())
                .eq(Delivery::getGroupCode,UserUtil.getBranchCode())
                .eq(Delivery::getIsCancel, NUM_ONE));
        log.info("波次出库确认，处理取消订单，获取未作废、已取消的订单，{}", deliveryList);
        //作废发货单总拣货数量
        BigDecimal totalPickingQuantity = BigDecimal.ZERO;
        //作废发货单总退拣数量
        BigDecimal totalRecheckRefundQuantity = BigDecimal.ZERO;
        //作废发货单总复核数量
        BigDecimal totalRecheckQuantity = BigDecimal.ZERO;
        //汇总作废单的总拣货数量、总退拣数量、总复核数量
        if (IterUtil.isNotEmpty(deliveryList)) {

            //获取取消还未更新成作废的订单
            List<Delivery> tempList = deliveryList.stream().filter(r -> !StrUtil.equals(r.getStatus(), DeliveryStatusEnum.VOID.getValue())).collect(toList());
            if (IterUtil.isNotEmpty(tempList)) {
                for (Delivery delivery : tempList) {
                    List<DeliveryDistribution> distributions = iDeliveryDistributionService.list(new QueryWrapper<DeliveryDistribution>().lambda()
                            .eq(DeliveryDistribution::getDeliveryCode, delivery.getCode())
                            .eq(DeliveryDistribution::getGroupCode,UserUtil.getBranchCode())
                            .eq(DeliveryDistribution::getDeleted, NumConst.NUM_ZERO));
                    Map<String, List<DeliveryDistribution>> listMap = distributions.stream().collect(Collectors.groupingBy(DeliveryDistribution::getInventoryCode));
                    for (Map.Entry<String, List<DeliveryDistribution>> entry : listMap.entrySet()) {
                        BigDecimal pickingQuantity = entry.getValue().stream().map(DeliveryDistribution::getPickingQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
                        BigDecimal recheckRefundQuantity = entry.getValue().stream().map(DeliveryDistribution::getRecheckRefundQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
                        BigDecimal recheckQuantity = pickingQuantity.subtract(recheckRefundQuantity);
                        iInventoryService.stockoutInventory(entry.getKey(), pickingQuantity, StockoutTypeEnum.OUT_CHECK.getValue(), outbound.getCode());
                        //更新出库明细、出库确认单信息
                        iOutboundItemService.update(new UpdateWrapper<OutboundItem>().lambda()
                                .eq(OutboundItem::getWavePickingCode, outbound.getWavePickingCode())
                                .eq(OutboundItem::getGroupCode, UserUtil.getBranchCode())
                                .eq(OutboundItem::getInventoryCode, entry.getKey())
                                .setSql(SqlConst.PLAN_QUANTITY_EQUALS_PLAN_QUANTITY + pickingQuantity.negate()).setSql(SqlConst.RECHECK_QUANTITY_EQUALS_RECHECK_QUANTITY + recheckQuantity.negate())
                                .setSql("recheck_unit_quantity=recheck_unit_quantity+" + recheckQuantity.negate()).setSql("recheck_refund_quantity=recheck_refund_quantity+" + recheckRefundQuantity.negate()));
                        iOutboundRegisterService.update(new UpdateWrapper<OutboundRegister>().lambda()
                                .eq(OutboundRegister::getWavePickingCode, outbound.getWavePickingCode())
                                .eq(OutboundRegister::getGroupCode,UserUtil.getBranchCode())
                                .eq(OutboundRegister::getInventoryCode, entry.getKey())
                                .setSql(SqlConst.PLAN_QUANTITY_EQUALS_PLAN_QUANTITY + pickingQuantity.negate()).setSql(SqlConst.RECHECK_QUANTITY_EQUALS_RECHECK_QUANTITY + recheckQuantity.negate()));

                        totalPickingQuantity = totalPickingQuantity.add(pickingQuantity);
                        totalRecheckRefundQuantity = totalRecheckRefundQuantity.add(recheckRefundQuantity);
                        totalRecheckQuantity = totalRecheckQuantity.add(recheckQuantity);
                    }

                }
                //更新出库单、出库明细单、出库确认单出的拣货数量、复核数量等
                iOutboundService.update(new UpdateWrapper<Outbound>().lambda()
                        .eq(Outbound::getCode, outbound.getCode())
                        .eq(Outbound::getGroupCode,UserUtil.getBranchCode())
                        .setSql(SqlConst.PLAN_QUANTITY_EQUALS_PLAN_QUANTITY + totalPickingQuantity.negate())
                        .setSql(SqlConst.RECHECK_QUANTITY_EQUALS_RECHECK_QUANTITY + totalRecheckQuantity.negate())
                        .setSql("recheck_refund_quantity=recheck_refund_quantity+" + totalRecheckRefundQuantity.negate()));


                List<String> cancelCodes = tempList.stream().map(Delivery::getCode).collect(toList());
                if (IterUtil.isNotEmpty(cancelCodes)) {
                    // 更新订单的状态，改为作废
                    iDeliveryService.update(new UpdateWrapper<Delivery>().lambda()
                            .in(Delivery::getCode, cancelCodes)
                            .eq(Delivery::getGroupCode,UserUtil.getBranchCode())
                            .eq(Delivery::getIsCancel, NUM_ONE)
                            .ne(Delivery::getStatus, DeliveryStatusEnum.VOID.getValue())
                            .set(Delivery::getStatus, DeliveryStatusEnum.VOID.getValue())
//                            .set(Delivery::getWaybillCode, "")
                    );
                    //TODO 如需自动取消面单号，放开上边和下边注释即可
                    //tmsWaybillUpdateService.asyncTmsWaybillDiscard(tempList);
                }
            }
            //获取波次号下未取消的订单数量
            int count = iDeliveryService.count(new LambdaQueryWrapper<Delivery>()
                    .eq(Delivery::getWavePickingCode, outbound.getWavePickingCode())
                    .eq(Delivery::getGroupCode,UserUtil.getBranchCode())
                    .ne(Delivery::getIsCancel, NUM_ONE));
            if (count == 0) {
                //设置出库单为取消出库状态
                iOutboundService.update(new UpdateWrapper<Outbound>().lambda()
                        .eq(Outbound::getCode, outbound.getCode())
                        .eq(Outbound::getGroupCode,UserUtil.getBranchCode())
                        .set(Outbound::getStatus, OutboundStatusEnum.CANCEL_OUT.getValue()));
                outbound.setStatus(OutboundStatusEnum.CANCEL_OUT.getValue());
            }
        }

    }


    /**
     * 已作废不在使用
     * @author liuxr
     * @date 2021/5/20 12:42
     * @param outbound
     * @param deliveryCodes
     * @return java.util.concurrent.Future<java.lang.Boolean>
     */
   /* @Async
    @Deprecated
    public Future<Boolean> outCheckByDeliveryCode(Outbound outbound, List<String> deliveryCodes) {
        boolean flag = outCheckByDeliveryCodeForTransactional(outbound, deliveryCodes);
        return new AsyncResult(flag);
    }*/


    /**
     * 订单出库确认，由于异步可能存在事务不回滚，所以单独放进一个方法中
     *
     * @param outbound      出库单
     * @param deliveryCodes 订单标识集合
     * @return
     */
   /* @Deprecated
    @Transactional(rollbackFor = {Exception.class})
    public boolean outCheckByDeliveryCodeForTransactional(Outbound outbound, List<String> deliveryCodes) {

        //获取波次号
        String wavePickingCode = outbound.getWavePickingCode();
        //获取所有订单信息
        List<Delivery> deliveries = iDeliveryService.list(new LambdaQueryWrapper<Delivery>().in(Delivery::getCode, deliveryCodes));

        if (IterUtil.isEmpty(deliveries)) {
            log.info("找不到符合条件的订单信息，波次号:{}", wavePickingCode);
            return false;
        }
        //没有获取到运单号的订单集合
        List<Delivery> noWillbillCodeDeliveries = deliveries.stream().filter(item -> StrUtil.isEmpty(item.getWaybillCode())).collect(Collectors.toList());
        //获取到运单号的订单集合
        List<Delivery> haveWillbillCodeDeliveries = deliveries.stream().filter(item -> StrUtil.isNotEmpty(item.getWaybillCode())).collect(Collectors.toList());

        try {
            if (IterUtil.isNotEmpty(noWillbillCodeDeliveries)) {
                //取得订单信息，同波次下所有订单物流信息、货主信息一样
                Delivery delivery = noWillbillCodeDeliveries.get(NUM_ZERO);

                TmsWaybillGetRequest request = new TmsWaybillGetRequest();
                // 配送公司编码
                request.setCpCode(delivery.getLogisticsCode());
                // 发件人信息
                UserInfoDto sender = new UserInfoDto();
                request.setSender(sender);
                // 发件人姓名
                sender.setName(delivery.getSenderName());
                // 发件人手机号
                sender.setMobile(delivery.getSenderMobile());
                // 寄件地址，这里的地址需要是卖家订购电子面单服务时使用的订购地址，具体可以通过TMS_WAYBILL_SUBSCRIPTION_QUERY接口获取
                AddressDto sendAddress = new AddressDto();
                sender.setAddress(sendAddress);
                sendAddress.setProvince(delivery.getSenderProvince());
                sendAddress.setCity(delivery.getSenderCity());
                sendAddress.setDistrict(delivery.getSenderDistrict());
                sendAddress.setDetail(delivery.getSenderDetailAddress());

                // 获取面单标准模板地址
                CloudprintStandardTemplates cloudprintStandardTemplates = cloudprintStandardTemplatesMapper.selectOne(new QueryWrapper<CloudprintStandardTemplates>().lambda().eq(CloudprintStandardTemplates::getCode, delivery.getLogisticsCode())
                        .eq(CloudprintStandardTemplates::getStandardWaybillType, NUM_ONE));
                if (null == cloudprintStandardTemplates) {
                    log.info("获取面单失败，没有物流模板");
                    this.iDeliveryService.update(new LambdaUpdateWrapper<Delivery>().in(Delivery::getCode, deliveryCodes)
                            .setSql(" remarks = remarks + '; 出库异常：获取面单失败，没有物流模板'"));
                    this.iDeliveryService.updateRemarks(deliveryCodes, "出库异常：获取面单失败，没有物流模板");
                    return false;
                }
                String standardTemplateUrl = cloudprintStandardTemplates.getStandardTemplateUrl();
                ArrayList<TradeOrderInfoDto> orderInfoDtos = new ArrayList<>();
                for (Delivery e : noWillbillCodeDeliveries) {
                    ArrayList<String> orderList = new ArrayList<>();
                    // 订单列表，这里的场景是一个订单获取一个面单号
                    orderList.add(e.getDeliveryOrderCode());
                    OrderInfoDto orderInfoDto = new OrderInfoDto();
                    SourcePlatformEnum oct = SourcePlatformEnum.getByValue(e.getSourcePlatformCode());
                    if (null == oct) {
                        orderInfoDto.setOrderChannelsType(SourcePlatformEnum.OTHERS.getValue());
                    } else {
                        orderInfoDto.setOrderChannelsType(e.getSourcePlatformCode());
                    }
                    orderInfoDto.setTradeOrderList(orderList);
                    List<DeliveryItem> deliveryItems = this.iDeliveryItemService.list(new LambdaQueryWrapper<DeliveryItem>()
                            .eq(DeliveryItem::getDeliveryCode, e.getCode()));
                    if (IterUtil.isEmpty(deliveryItems)) {
                        log.info("找不到订单明细，波次号:{}，订单标识：{}", wavePickingCode, e.getCode());
                        continue;
                    }
                    List<Item> items = deliveryItems.stream().map(e1 -> {
                        Item item = new Item();
                        item.setName(e1.getGoodsName());
                        item.setCount(e1.getQuantity().intValue());
                        return item;
                    }).collect(toList());

                    PackageInfoDto packageInfoDto = new PackageInfoDto();
                    packageInfoDto.setItems(items);

                    AddressDto receiveAddress = new AddressDto();
                    receiveAddress.setProvince(e.getReceiverProvince());
                    receiveAddress.setCity(e.getReceiverCity());
                    receiveAddress.setDistrict(e.getReceiverDistrict());
                    receiveAddress.setDetail(e.getReceiverDetailAddress());

                    UserInfoDto receiver = new UserInfoDto();
                    receiver.setName(e.getReceiverName());
                    receiver.setMobile(e.getReceiverMobile());
                    receiver.setAddress(receiveAddress);

                    TradeOrderInfoDto tradeOrderInfoDto = new TradeOrderInfoDto();
                    tradeOrderInfoDto.setObjectId(e.getCode());
                    tradeOrderInfoDto.setTemplateUrl(standardTemplateUrl);
                    tradeOrderInfoDto.setOrderInfo(orderInfoDto);
                    tradeOrderInfoDto.setPackageInfo(packageInfoDto);
                    tradeOrderInfoDto.setRecipient(receiver);

                    orderInfoDtos.add(tradeOrderInfoDto);
                }
                SendSysParams params = new SendSysParams();
                params.setFromCode(dailyToken);

                request.setTradeOrderInfoDtos(orderInfoDtos);
                log.info("调用获取面单接口");
                log.info("交易对象：{}", orderInfoDtos);

                PacClient client = new PacClient(dailyAppKey, dailySecretKey, dailyUrl);
                TmsWaybillGetResponse response = client.send(request, params);
                if (!response.isSuccess()) {
                    if ("S99".equals(response.getErrorCode())) {
                        log.error("获取面单超时，进行【1】次重试...");
                        response = client.send(request, params);
                        if (!response.isSuccess()) {
                            log.error(" errorCode:{},errorMessage:{}", response.getErrorCode(), response.getErrorMsg());
                            if ("S99".equals(response.getErrorCode())) {
                                log.error("获取面单超时，进行【2】次重试...");
                                response = client.send(request, params);
                                if (!response.isSuccess()) {
                                    //获取面单失败，更新订单发货状态
                                    log.error(GET_WAY_BILL_HAS_EXCEPTION_UPDATE_DELIVERY_STATUS_UN_DELIVERD, deliveryCodes, response.getErrorCode(), response.getErrorMsg());
                                    this.iDeliveryService.updateRemarks(deliveryCodes, StrUtil.format(GET_WAY_BILL_HAS_EXCEPTION_UPDATE_DELIVERY_STATUS_UN_DELIVERD, deliveryCodes, response.getErrorCode(), response.getErrorMsg()));
                                    return false;
                                }
                            } else {
                                //获取面单失败，更新订单发货状态
                                log.error(GET_WAY_BILL_HAS_EXCEPTION_UPDATE_DELIVERY_STATUS_UN_DELIVERD, deliveryCodes, response.getErrorCode(), response.getErrorMsg());
                                this.iDeliveryService.updateRemarks(deliveryCodes, StrUtil.format(GET_WAY_BILL_HAS_EXCEPTION_UPDATE_DELIVERY_STATUS_UN_DELIVERD, deliveryCodes, response.getErrorCode(), response.getErrorMsg()));

                                return false;
                            }
                        }
                    } else {
                        //获取面单失败，更新订单发货状态
                        log.error(GET_WAY_BILL_HAS_EXCEPTION_UPDATE_DELIVERY_STATUS_UN_DELIVERD, deliveryCodes, response.getErrorCode(), response.getErrorMsg());
                        this.iDeliveryService.updateRemarks(deliveryCodes, StrUtil.format(GET_WAY_BILL_HAS_EXCEPTION_UPDATE_DELIVERY_STATUS_UN_DELIVERD, deliveryCodes, response.getErrorCode(), response.getErrorMsg()));

                        return false;
                    }
                }
                List<WaybillCloudPrintResponse> waybillCloudPrintResponseList = response.getWaybillCloudPrintResponseList();

                //按订单号将订单集合转成map
                Map<String, Delivery> deliveryMap = deliveries.stream().collect(Collectors.toMap(Delivery::getCode, e -> e));
                for (WaybillCloudPrintResponse waybillCloudPrintResponse : waybillCloudPrintResponseList) {
                    String code = waybillCloudPrintResponse.getObjectId();
                    String wayBillCode = waybillCloudPrintResponse.getWaybillCode();
                    JSONObject printData = JSONUtil.parseObj(waybillCloudPrintResponse.getPrintData());
                    String signature = printData.get("signature").toString();
                    String url = printData.get("templateURL").toString();
                    log.info("更新订单【{}】状态,面单号【{}】", code, wayBillCode);

                    String routeCode = "";

                    JSONObject data = JSONUtil.parseObj(printData.get("data"));
                    if (null != data) {
                        JSONObject routingInfo = JSONUtil.parseObj(data.get("routingInfo"));
                        if (null != routingInfo) {
                            if (null != routingInfo.get("routeCode")) {
                                routeCode = routingInfo.get("routeCode").toString();
                            }
                        }
                    }
                    //更新订单物流信息
                    this.iDeliveryService.updateWayBillCode(code, wayBillCode, signature, url, waybillCloudPrintResponse.getPrintData(), routeCode);
                    Delivery item = iDeliveryService.getOne(new LambdaQueryWrapper<Delivery>().eq(Delivery::getCode, code));
                    item.setWaybillCode(wayBillCode);
                    item.setSignature(signature);
                    item.setTemplateUrl(url);
                    haveWillbillCodeDeliveries.add(item);
                }


                List<String> success = waybillCloudPrintResponseList.stream().map(WaybillCloudPrintResponse::getObjectId).collect(toList());
                // 如果没获取到面单号，则将状态更新为 待发货
                List<String> reduce = success.stream().filter(item -> !deliveryCodes.contains(item)).collect(toList());
                if (IterUtil.isNotEmpty(reduce)) {
                    log.info("未获取到面单号,更新订单状态，订单标识{}", reduce);
                    iDeliveryService.update(new UpdateWrapper<Delivery>().lambda().in(Delivery::getCode, reduce)
                            .set(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY.getValue())
                            .set(Delivery::getRemarks, "未获取到面单号"));

                }
            }
            //订单确认及数据回显
            orderConfirmAndReturnDeliveryData(haveWillbillCodeDeliveries);
        } catch (Exception e) {
            log.error("订单出库异常：{}", e.getMessage());
            return false;
        }
        return true;
    }
*/

    /**
     * 订单确认及数据回显
     *
     * @param haveWillbillCodeDeliveries 订单信息
     */
    private void orderConfirmAndReturnDeliveryData(List<Delivery> haveWillbillCodeDeliveries) {
        for (Delivery delivery : haveWillbillCodeDeliveries) {
            // 更新奇门状态
            boolean qimenUpdateStatusFlag = false;
            String deliveryCode = delivery.getCode();
            try {
                qimenUpdateStatusFlag = deliveryorderConfirm(delivery);
            } catch (Exception e) {
                iDeliveryService.update(new UpdateWrapper<Delivery>().lambda()
                        .eq(Delivery::getCode, deliveryCode)
                        .eq(Delivery::getGroupCode,UserUtil.getBranchCode())
                        .set(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY.getValue()));
            }
            // 奇门订单确认成功
            if (qimenUpdateStatusFlag) {
                // 订单状态如果是已复核的则更新订单状态为“已出库”，更新明细、分配单的出库数量
                iDeliveryService.update(new UpdateWrapper<Delivery>().lambda()
                        .eq(Delivery::getCode, deliveryCode)
                        .eq(Delivery::getGroupCode,UserUtil.getBranchCode())
                        .eq(Delivery::getIsCancel, NUM_ZERO).eq(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY.getValue())
                        .set(Delivery::getStatus, DeliveryStatusEnum.DELIVERIED.getValue())
                        .setSql(" send_num = recheck_quantity")
                );
                // TODO 应该明细更新也得校验、分配更新也得校验，不过这样校验较多，需要考虑优化, 还有更新失败时的处理
                iDeliveryItemService.update(new UpdateWrapper<DeliveryItem>().lambda()
                        .eq(DeliveryItem::getDeliveryCode, deliveryCode)
                        .eq(DeliveryItem::getGroupCode,UserUtil.getBranchCode())
                        .eq(DeliveryItem::getDeleted, NUM_ZERO)
                        .setSql(" outbound_quantity = plan_quantity"));

                boolean distributionUpdateFlag = iDeliveryDistributionService.update(new UpdateWrapper<DeliveryDistribution>()
                        .lambda()
                        .eq(DeliveryDistribution::getDeliveryCode, deliveryCode)
                        .eq(DeliveryDistribution::getGroupCode,UserUtil.getBranchCode())
                        .eq(DeliveryDistribution::getDeleted, NUM_ZERO)
                        .setSql(" outbound_quantity = distribution_quantity"));

                // 根据订单的分配单减少临时库存
                //订单的总数量等于订单的出库数量(等同于复核数量)时，更新库存
                if (delivery.getTotalNum().compareTo(delivery.getRecheckQuantity()) == NUM_ZERO && distributionUpdateFlag) {
                    List<DeliveryDistribution> deliveryDistributionList = iDeliveryDistributionService.list(new QueryWrapper<DeliveryDistribution>().lambda()
                            .eq(DeliveryDistribution::getDeliveryCode, deliveryCode)
                            .eq(DeliveryDistribution::getGroupCode,UserUtil.getBranchCode())
                            .eq(DeliveryDistribution::getDeleted, NUM_ZERO));
                    for (DeliveryDistribution deliveryDistribution : deliveryDistributionList) {
                        iInventoryService.update(new UpdateWrapper<Inventory>().lambda().eq(Inventory::getCode, deliveryDistribution.getInventoryCode())
                                .setSql("quantity=quantity+" + deliveryDistribution.getDistributionQuantity().negate()));
                    }
                }
            }
        }
    }

    /**
     * 发送订单确认消息
     *
     * @param delivery 订单信息
     */
    private boolean deliveryorderConfirm(Delivery delivery) throws ApiException {

        log.info("订单出库完成发送确认信息：{}", delivery.getCode());
        DeliveryorderConfirmRequest req = new DeliveryorderConfirmRequest();
        req.setCustomerId(delivery.getCustomerId());
        req.setVersion("2.0");
        DeliveryorderConfirmRequest.DeliveryOrder deliveryOrder = new DeliveryorderConfirmRequest.DeliveryOrder();
        // 出库单号
        deliveryOrder.setDeliveryOrderCode(delivery.getDeliveryOrderCode());
        // 仓库编码
//        deliveryOrder.setWarehouseCode(delivery.getWarehouseCode());
        deliveryOrder.setWarehouseCode(delivery.getWarehouseSerialNumber());
        // 出库单类型
        deliveryOrder.setOrderType(delivery.getOrderType());


        // 商品列表
        List<DeliveryorderConfirmRequest.Item> items = new ArrayList<>();
        List<DeliveryItem> deliveryItemList = this.iDeliveryItemService.list(new QueryWrapper<DeliveryItem>().lambda()
                .eq(DeliveryItem::getDeliveryCode, delivery.getCode())
                .eq(DeliveryItem::getGroupCode,UserUtil.getBranchCode()));
        deliveryItemList.forEach(e -> {
            DeliveryorderConfirmRequest.Item item = new DeliveryorderConfirmRequest.Item();
            // 商品编码
            item.setItemCode(e.getGoodCode());
            // 包裹内该商品数量
            item.setQuantity(e.getPlanQuantity().longValue());
            items.add(item);
        });

        // 包裹信息
        List<DeliveryorderConfirmRequest.Package> packages = new ArrayList<>();
        DeliveryorderConfirmRequest.Package package1 = new DeliveryorderConfirmRequest.Package();
        // 物流公司编码
        package1.setLogisticsCode(delivery.getLogisticsCode());
        // 运单号
        package1.setExpressCode(delivery.getWaybillCode());
        package1.setItems(items);
        packages.add(package1);

        req.setPackages(packages);
        deliveryOrder.setItems(items);

        // 单据列表
        List<DeliveryorderConfirmRequest.OrderLine> orderLines = new ArrayList<>();
        deliveryItemList.forEach(e -> {
            DeliveryorderConfirmRequest.OrderLine orderLine = new DeliveryorderConfirmRequest.OrderLine();
            // 商品编码
            orderLine.setItemCode(e.getGoodCode());
            // 应发商品数量
            orderLine.setPlanQty((e.getPlanQuantity().intValue()) + "");
            // 实发商品数量
            orderLine.setActualQty((e.getPlanQuantity().intValue()) + "");
            orderLine.setActualPrice(e.getActualPrice().toString());

            orderLines.add(orderLine);
        });

        req.setDeliveryOrder(deliveryOrder);
        req.setOrderLines(orderLines);
        QimenClient client = new DefaultQimenClient(url, appkey, secret);
        DeliveryorderConfirmResponse rsp = client.execute(req);
        log.info("发货单确认接口返回：{}", rsp.getMessage());

        if (StrUtil.equals(rsp.getFlag(), "success")) {
            return true;
        } else {
            List<String> codes = new ArrayList<>();
            codes.add(delivery.getCode());
            //更新订单确认失败原因
            this.iDeliveryService.updateRemarks(codes, StrUtil.format("订单确认失败，{}", rsp.getMessage()), UserUtil.getBranchCode());
            return false;
        }
    }


    /**
     * 出库确认
     *
     * @param outbound 出库单信息
     * @param isReview 是否跳过复核，true 跳过，false 不跳过
     * @return
     */
    @Transactional(rollbackFor = {Exception.class})
    public Result confirmDelivery(Outbound outbound, boolean isReview) {
        String groupCode = UserUtil.getBranchCode();
        outbound.setGroupCode(groupCode);
        log.info("出库单确认出库，是否跳过复核：{}；出库单信息：{}", isReview, JSONUtil.toJsonStr(outbound));
        Map<String, Object> resultMap = new HashMap<>();
        if (OutboundStatusEnum.CANCEL_OUT.getValue().equals(outbound.getStatus())) {
            resultMap = returnResultMap(500, "订单已作废，出库单取消出库");
            return Result.ok().data(resultMap);
        }
        // 根据出库单波次号获取所有状态为待出库、有面单号、计划数量=复核数量、未废除的订单号
        log.info("出库确认：【step2】，获取状态为待出库、出库数量等于计划数量、未取消、有运单号的订单");
        List<Delivery> deliveries = iDeliveryService.findDeliveryForConfirmDelivery(outbound.getWavePickingCode(),groupCode);

        //订单是按单拣货，走原来的出库逻辑（仅限按单拣货的情况）
        Picking picking = pickingService.getOne(new LambdaQueryWrapper<Picking>()
                .eq(Picking::getWavePickingCode, outbound.getWavePickingCode())
                .eq(Picking::getGroupCode,groupCode));
        if (OrderTagEnum.QUANTITY.getValue().equals(picking.getOrderTag())) {
            Delivery delivery = iDeliveryService.getOne(new LambdaQueryWrapper<Delivery>()
                    .eq(Delivery::getWavePickingCode, outbound.getWavePickingCode())
                    .eq(Delivery::getGroupCode,groupCode));
            if (deliveries.size() == NUM_ZERO) {
                if ("qimen".equals(delivery.getSourceType())) {
                    resultMap = returnResultMap(500, "订单" + delivery.getCode() + "没有面单号，无法回调奇门订单确认接口，请确认");
                    return Result.ok().data(resultMap);
                }
            }
            return iOutboundService.outCheck(outbound);
        }

        log.info("根据出库单波次号【{}】获取所有状态为待出库、有面单号、计划数量=复核数量、未废除的订单,【{}】", outbound.getWavePickingCode(), deliveries.size());

        deliveries.forEach(r -> {
            try {
                log.info("出库确认，遍历扣库存，更新订单状态、回写ERP，回写失败订单状态及库存回滚。订单号：{}", r.getCode());
                confirmDeliveryUpdateDeliveryStatusService.updateInventory(r);
            } catch (Exception e) {
                log.error("订单【{}】状态更新失败,{}", r.getCode(), e.getMessage());
                iDeliveryService.update(Wrappers.lambdaUpdate(Delivery.class)
                        .eq(Delivery::getCode,r.getCode())
                        .eq(Delivery::getGroupCode,groupCode)
                        .set(Delivery::getRemarks,e.getMessage()));
            }
        });


        // 释放容器
        List<String> containerCodes = iDeliveryDistributionService.getContainerCodesForOutCheck(outbound.getWavePickingCode(),groupCode);
        iContainerService.batchReleaseContainerForEmptyInventory(containerCodes,groupCode);


        return Result.ok();
    }

    /**
     * 出库确认更新出库单状态
     *
     * @param outboundCode    出库单号
     * @param wavePickingCode 波次单号
     * @author liuxr
     * @date 2021/5/15 10:32
     */
    public void outConfirm(String outboundCode, String wavePickingCode,String groupCode) {
        //获取所有订单状态分组
        Set<String> statusSet = iDeliveryService.findDeliveryStatusForConfirmDelivery(wavePickingCode,groupCode);
        if (CollUtil.isEmpty(statusSet)) {
            log.error("出库确认失败，找不到波次内的订单状态。波次号:{}", wavePickingCode);
            throw new ServiceException("出库确认失败,订单状态异常！");
        }
        String status = OutboundStatusEnum.NOT_OUT.getValue();
        if (CollUtil.contains(statusSet, DeliveryStatusEnum.UN_RECHECK.getValue())) {
            //如果有待复核，出库单状态就是待复核
            status = OutboundStatusEnum.NOT_RECHECK.getValue();
        } else if (CollUtil.contains(statusSet, DeliveryStatusEnum.UN_DELIVERY.getValue()) && CollUtil.contains(statusSet, DeliveryStatusEnum.DELIVERIED.getValue())) {
            //如果有待出库和出库完成，出库单状态就是部分出库
            status = OutboundStatusEnum.PAET_OUT.getValue();
        } else if (CollUtil.contains(statusSet, DeliveryStatusEnum.UN_DELIVERY.getValue())) {
            //如果只有待出库，出库单状态就是待出库
            status = OutboundStatusEnum.NOT_OUT.getValue();
        } else if (CollUtil.contains(statusSet, DeliveryStatusEnum.DELIVERIED.getValue()) && CollUtil.contains(statusSet, DeliveryStatusEnum.VOID.getValue())) {
            //如果有作废和出库完成，出库单状态就是部分出库
            status = OutboundStatusEnum.FINISH_OUT.getValue();
        } else if (CollUtil.contains(statusSet, DeliveryStatusEnum.DELIVERIED.getValue())) {
            //如果只有出库完成，出库单状态就是出库完成
            status = OutboundStatusEnum.FINISH_OUT.getValue();
        } else if (CollUtil.contains(statusSet, DeliveryStatusEnum.VOID.getValue())) {
            status = OutboundStatusEnum.CANCEL_OUT.getValue();
        }



        log.info("出库确认：【step4】，根据波次内出库完成的订单出库数量更新出库单出库数量");
        //根据波次内出库完成的订单出库数量更新出库单出库数量
        outboundMapper.updateOutQuantity(outboundCode,groupCode);
        log.info("出库确认：【step5】，更新出库单状态，{}", status);
        //更新出库单状态
        outboundMapper.updateStatusForConfirmDelivery(outboundCode, status,groupCode);

        //按单调用中台扣费接口(出库正常完成调用扣费接口)
        if(status.equalsIgnoreCase(OutboundStatusEnum.FINISH_OUT.getValue())){
            Outbound outbound = iOutboundService.getOne(new QueryWrapper<Outbound>().lambda()
                    .eq(Outbound::getCode, outboundCode)
                    .eq(Outbound::getGroupCode,groupCode)
                    .eq(Outbound::getDeleted, "0"));
            if(outbound.getType().equalsIgnoreCase("0")){ //只针对销售出库的进行扣费
                deductionByBill(outbound);
            }
        }
    }

    /**
     * 出库确认根据订单状态更新出库单状态（一个订单对应一个出库单的情况）
     *
     * @param outboundCode    出库单号
     * @param deliveryCode 订单号
     * @author liuxr
     * @date 2021/5/15 10:32
     */
    public void outConfirmByDelivery(String outboundCode, String deliveryCode) {

        Delivery delivery = iDeliveryService.getOne(new LambdaQueryWrapper<Delivery>()
                .eq(Delivery::getCode, deliveryCode)
                .eq(Delivery::getGroupCode,UserUtil.getBranchCode()));
        if (null == delivery) {
            log.error("出库确认失败，找不到对应的订单。订单号:{}", deliveryCode);
            throw new ServiceException("出库确认失败,没有找到出库订单！");
        }
        String status = OutboundStatusEnum.NOT_OUT.getValue();
        //出库单状态和订单状态保持一致
        if (DeliveryStatusEnum.UN_DELIVERY.getValue().equals(delivery.getStatus())) {
            status = OutboundStatusEnum.NOT_OUT.getValue();
        } else if (DeliveryStatusEnum.DELIVERIED.getValue().equals(delivery.getStatus())) {
            status = OutboundStatusEnum.FINISH_OUT.getValue();
        } else if (DeliveryStatusEnum.UN_RECHECK.getValue().equals(delivery.getStatus())) {
            status = OutboundStatusEnum.NOT_RECHECK.getValue();
        } else if (delivery.getIsCancel() == 1) {
            status = OutboundStatusEnum.CANCEL_OUT.getValue();
        }

        log.info("出库确认：【step4】，根据波次内出库完成的订单出库数量更新出库单出库数量");
        //根据波次内出库完成的订单出库数量更新出库单出库数量
        outboundMapper.updateOutQuantity(outboundCode,UserUtil.getBranchCode());
        log.info("出库确认：【step5】，更新出库单状态，{}", status);
        //更新出库单状态
        outboundMapper.updateStatusForConfirmDelivery(outboundCode, status,UserUtil.getBranchCode());

        //按单调用中台扣费接口(出库正常完成调用扣费接口)
        if(status.equalsIgnoreCase(OutboundStatusEnum.FINISH_OUT.getValue())){
            Outbound outbound = iOutboundService.getOne(new QueryWrapper<Outbound>().lambda()
                    .eq(Outbound::getCode, outboundCode)
                    .eq(Outbound::getGroupCode,UserUtil.getBranchCode())
                    .eq(Outbound::getDeleted, "0"));
            deductionByBill(outbound);
        }

    }

    public void deductionByBill(Outbound outbound) {
        //按单收费（一个订单对应一个出库单）
        MiddlegroundOutBound middlegroundOutBound = new MiddlegroundOutBound();
        middlegroundOutBound.setOutboundCode(outbound.getCode());
        middlegroundOutBound.setWarehouseCode(outbound.getWarehouseCode());
        middlegroundOutBound.setWarehouseName(warehouseCache.translate(outbound.getWarehouseCode()));
        middlegroundOutBound.setShipmentCode(outbound.getShipmentCode());
        middlegroundOutBound.setShipmentName(outbound.getShipmentName());
        middlegroundOutBound.setPlanQuantity(outbound.getPlanQuantity());
        middlegroundOutBound.setOutQuantity(outbound.getOutQuantity());
        middlegroundOutBound.setType(outbound.getType());
        middlegroundOutBound.setWavePickingCode(outbound.getWavePickingCode());
        middlegroundOutBound.setOrderTag(outbound.getOrderTag());
        middlegroundOutBound.setCreatedBy(outbound.getCreatedBy());
        middlegroundOutBound.setModifiedBy(outbound.getModifiedBy());
        ResultDO resultDO = arpaCloudUtil.jinchangOrderDetailCharge(middlegroundOutBound, outbound.getGroupCode());
        if(resultDO.getStatus() != 0){
            throw new ServiceException(resultDO.getMsg());
        }
    }

    private HashMap<String, Object> returnResultMap(Integer status, String msg) {
        HashMap<String, Object> resultMap = new HashMap<>();
        resultMap.put("status", status);
        resultMap.put("msg", msg);
        return resultMap;
    }

    /**
     * 批量出库处理订单取消
     *
     * @param outbound
     */
    private void handleDeliveryCancel(Outbound outbound) {
        String groupCode = outbound.getGroupCode();
        //获取所有取消的订单
        List<Delivery> deliveryList = iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                .select(Delivery::getCode, Delivery::getStatus,Delivery::getCarrierCode)
                .eq(Delivery::getWavePickingCode, outbound.getWavePickingCode())
                .eq(Delivery::getGroupCode,groupCode)
                .eq(Delivery::getIsCancel, NUM_ONE));
        log.info("波次出库确认，处理取消订单，获取未作废、已取消的订单，{}", deliveryList);
        //作废发货单总拣货数量
        BigDecimal totalPickingQuantity;
        //作废发货单总退拣数量
        BigDecimal totalRecheckRefundQuantity;
        //作废发货单总复核数量
        BigDecimal totalRecheckQuantity;
        //汇总作废单的总拣货数量、总退拣数量、总复核数量
        if (IterUtil.isNotEmpty(deliveryList)) {

            //获取取消还未更新成作废的订单
            List<Delivery> tempList = deliveryList.stream().filter(r -> !StrUtil.equals(r.getStatus(), DeliveryStatusEnum.VOID.getValue())).collect(toList());


            if (IterUtil.isNotEmpty(tempList)) {

                //获取所有取消未作废的订单号
                List<String> deliveryCodes = tempList.parallelStream().map(Delivery::getCode).collect(toList());

                //根据订单号获取所有分配明细
                List<DeliveryDistribution> deliveryDistributions = iDeliveryDistributionService.list(new LambdaQueryWrapper<DeliveryDistribution>()
                        .select(DeliveryDistribution::getCode,
                                DeliveryDistribution::getDeliveryCode,
                                DeliveryDistribution::getInventoryCode,
                                DeliveryDistribution::getOldInventoryCode,
                                DeliveryDistribution::getDistributionQuantity,
                                DeliveryDistribution::getRecheckRefundQuantity,
                                DeliveryDistribution::getRecheckQuantity,
                                DeliveryDistribution::getPickingQuantity,
                                DeliveryDistribution::getPickStockOutNum)
                        .in(DeliveryDistribution::getDeliveryCode, deliveryCodes)
                        .eq(DeliveryDistribution::getGroupCode,groupCode));

                //将所有的分配明细按订单号分组
                Map<String, List<DeliveryDistribution>> distList = deliveryDistributions.stream().collect(Collectors.groupingBy(DeliveryDistribution::getDeliveryCode));


                //通过分配明细获取所有临时库存标识
                Set<String> tempInventoryCodeSet = deliveryDistributions.parallelStream().map(DeliveryDistribution::getInventoryCode).collect(Collectors.toSet());
                //通过临时库存、原始库存获取所有库存信息
                List<Inventory> inventories = iInventoryService.list(new LambdaQueryWrapper<Inventory>().in(Inventory::getCode, tempInventoryCodeSet));
                //将库存信息通过库存code转成map
                Map<String, Inventory> inventoryMap = inventories.stream().collect(Collectors.toMap(Inventory::getCode, r -> r));

                //遍历取消订单集合获取订单要减少的临时库存、要增加的原始库存、操作数量
                Map<String, Map<String, Object>> map = tempList.parallelStream()
                        .collect(Collectors.toMap(Delivery::getCode, delivery -> {

                            //根据订单号获取订单的分配明细
                            List<DeliveryDistribution> distributions = distList.get(delivery.getCode());
                            Map<String, List<DeliveryDistribution>> listMap = distributions.parallelStream()
                                    .collect(Collectors.groupingBy(DeliveryDistribution::getInventoryCode));
                            //将分配明细按照临时库存标识分组，计算临时库存需要减少的数量
                            //返回 Map<临时库存标识,合计分配数量> 要减少库存的临时库存及数量
                            List<InventoryOperationDto> pickingQuantitys = listMap.entrySet().parallelStream()
                                    .map(m -> {
                                        InventoryOperationDto dto = new InventoryOperationDto();
                                        dto.setUuid(IdUtil.simpleUUID());
                                        dto.setInventoryCode(m.getKey());
                                        dto.setInventory(inventoryMap.get(m.getKey()));
                                        dto.setQuantity(m.getValue()
                                                .stream()
                                                .map(DeliveryDistribution::getPickingQuantity)
                                                .reduce(BigDecimal.ZERO, BigDecimal::add));
                                        dto.setRecheckRefundQuantity(m.getValue()
                                                .stream()
                                                .map(DeliveryDistribution::getRecheckRefundQuantity)
                                                .reduce(BigDecimal.ZERO, BigDecimal::add));
                                        dto.setRecheckQuantity((dto.getQuantity()).subtract(dto.getRecheckRefundQuantity()));
                                        return dto;

                                    })
                                    .collect(Collectors.toList());
                            Map<String, Object> cancelQuantityMap = new HashMap<>();
                            cancelQuantityMap.put("deliveryCode", delivery);
                            cancelQuantityMap.put("pickingQuantitys", pickingQuantitys);
                            return cancelQuantityMap;
                        }));


                //要减少库存的临时库存总集合
                List<InventoryOperationDto> subtract = new ArrayList<>();
                //所有库存操作记录集合
                List<InventoryRecord> inventoryRecords = new ArrayList<>();

                //退减库存
                List<Inventory> reInventorys = new ArrayList<>();
                // 获取退减临时库位
                Location reLocation = iLocationService.getOne(new LambdaQueryWrapper<Location>().select(Location::getCode)
                        .eq(Location::getWarehouseCode, outbound.getWarehouseCode())
                        .eq(Location::getGroupCode,groupCode)
                        .eq(Location::getSerialNumber, TempLocationTypeEnum.RE));
                if (null == reLocation) {
                    log.error("出库单【{}】出库失败，仓库【{}】缺少退减库位", outbound.getCode(), outbound.getWarehouseCode());
                    throw new ServiceException("拣货确认失败，仓库缺少退减库位");
                }
                //遍历合并所有要处理的信息
                map.forEach((k, v) -> {
                    List<InventoryOperationDto> pickingQuantitys = (List<InventoryOperationDto>) v.get("pickingQuantitys");
                    //合并所有要减少库存的临时库存
                    subtract.addAll(pickingQuantitys);

                    //创建减库存操作记录
                    pickingQuantitys.forEach(r -> {
                        //临时库存减库存操作记录
                        InventoryRecord record = new InventoryRecord();
                        BeanUtil.copyProperties(r.getInventory(), record);
                        record.setCode(IdUtil.simpleUUID());
                        record.setGroupCode(groupCode);
                        record.setInventoryCode(r.getInventoryCode());
                        record.setSourceCode(k);
                        record.setNum(r.getQuantity().negate());
                        record.setType(InventoryRecordTypeEnum.OUTBOUND.getValue());
                        inventoryRecords.add(record);

                        //退减库存信息
                        Inventory inventory = new Inventory();
                        BeanUtil.copyProperties(r.getInventory(), inventory);
                        inventory.setCode(IdUtil.simpleUUID());
                        inventory.setUseQuantity(BigDecimal.ZERO);
                        inventory.setOldInventoryCode(StrUtil.EMPTY);
                        inventory.setTempCode(StrUtil.EMPTY);
                        inventory.setTempItemCode(StrUtil.EMPTY);
                        inventory.setTempRegisterCode(StrUtil.EMPTY);
                        inventory.setLocation(reLocation.getCode());
                        inventory.setQuantity(r.getQuantity());
                        // 抹去容器号等信息
                        inventory.setContainerBarCode(StrUtil.EMPTY);
                        reInventorys.add(inventory);

                        //退减库存擦插入操作记录
                        InventoryRecord reRecord = new InventoryRecord();
                        BeanUtil.copyProperties(inventory, reRecord);
                        reRecord.setCode(IdUtil.simpleUUID());
                        reRecord.setGroupCode(groupCode);
                        reRecord.setInventoryCode(r.getInventoryCode());
                        reRecord.setSourceCode(k);
                        reRecord.setNum(r.getQuantity());
                        reRecord.setType(InventoryRecordTypeEnum.OUTBOUND.getValue());
                        inventoryRecords.add(reRecord);
                    });


                });


                // 接收处理后的数据
                List<InventoryOperationDto> subtractData = getInventoryOperationDtos(subtract);

                //减少临时库存
                inventoryOperationService.subtractBatch(subtractData);


                List<Inventory> reInventoryDatas = new ArrayList<>();
                // 数据分组统计处理
                reInventorys.parallelStream()
                        .collect(Collectors.groupingBy(Inventory::getOldInventoryCode, Collectors.toList()))
                        .forEach((inventoryCode, dtos) -> {
                            dtos.stream()
                                    .reduce((a, b) -> {
                                        Inventory inventory = new Inventory();
                                        BeanUtil.copyProperties(a, inventory);
                                        inventory.setCode(IdUtil.simpleUUID());
                                        inventory.setQuantity(a.getQuantity().add(b.getQuantity()));
                                        return inventory;
                                    })
                                    .ifPresent(reInventoryDatas::add);
                        });

                //批量插入退减库存 TODO 未合并
                iInventoryService.saveBatch(reInventoryDatas);

                //批量插入操作记录
                iInventoryRecordService.saveBatch(inventoryRecords);


                List<InventoryOperationDto> allData = subtractData.parallelStream().peek(r -> r.setInventoryCode(r.getInventory().getOldInventoryCode())).collect(toList());
                allData = getInventoryOperationDtos(allData);


                //更新出库单明细
                iOutboundItemService.deliveryCancelUpdataQuantity(outbound.getWavePickingCode(), allData);
                //更新出库确认明细
                iOutboundRegisterService.deliveryCancelUpdataQuantity(outbound.getWavePickingCode(), allData);
                //更新出库单

                totalPickingQuantity = subtractData.parallelStream().map(InventoryOperationDto::getQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
                totalRecheckQuantity = subtractData.parallelStream().map(InventoryOperationDto::getRecheckQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
                totalRecheckRefundQuantity = subtractData.parallelStream().map(InventoryOperationDto::getRecheckRefundQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);

                //更新出库单、出库明细单、出库确认单出的拣货数量、复核数量等
                iOutboundService.update(new UpdateWrapper<Outbound>().lambda()
                        .eq(Outbound::getCode, outbound.getCode())
                        .eq(Outbound::getGroupCode, UserUtil.getBranchCode())
                        .setSql(SqlConst.PLAN_QUANTITY_EQUALS_PLAN_QUANTITY + totalPickingQuantity.negate())
                        .setSql(SqlConst.RECHECK_QUANTITY_EQUALS_RECHECK_QUANTITY + totalRecheckQuantity.negate())
                        .setSql("recheck_refund_quantity=recheck_refund_quantity+" + totalRecheckRefundQuantity.negate()));
                // 更新订单的状态，改为作废
                iDeliveryService.update(new UpdateWrapper<Delivery>().lambda()
                        .in(Delivery::getCode, deliveryCodes)
                        .eq(Delivery::getGroupCode, UserUtil.getBranchCode())
                        .eq(Delivery::getIsCancel, NUM_ONE)
                        .set(Delivery::getStatus, DeliveryStatusEnum.VOID.getValue())
//                        .set(Delivery::getWaybillCode, "")
                );
                //自动取消面单号，放开上边和下边注释即可
                //tmsWaybillUpdateService.asyncTmsWaybillDiscard(tempList);
            }
            //获取波次号下未取消的订单数量
            int count = iDeliveryService.count(new LambdaQueryWrapper<Delivery>()
                    .eq(Delivery::getWavePickingCode, outbound.getWavePickingCode())
                    .eq(Delivery::getGroupCode, UserUtil.getBranchCode())
                    .ne(Delivery::getIsCancel, NUM_ONE));
            if (count == 0) {
                //设置出库单为取消出库状态
                iOutboundService.update(new UpdateWrapper<Outbound>().lambda()
                        .eq(Outbound::getCode, outbound.getCode())
                        .eq(Outbound::getGroupCode, UserUtil.getBranchCode())
                        .set(Outbound::getStatus, OutboundStatusEnum.CANCEL_OUT.getValue()));
                outbound.setStatus(OutboundStatusEnum.CANCEL_OUT.getValue());
            }
        }

    }

    @NotNull
    private List<InventoryOperationDto> getInventoryOperationDtos(List<InventoryOperationDto> dto) {
        List<InventoryOperationDto> data = new ArrayList<>();
        // 数据分组统计处理
        dto.parallelStream()
                .collect(Collectors.groupingBy(InventoryOperationDto::getInventoryCode, Collectors.toList()))
                .forEach((inventoryCode, dtos) -> {
                    dtos.stream()
                            .reduce((a, b) -> new InventoryOperationDto(IdUtil.simpleUUID(),
                                    a.getInventoryCode(), a.getQuantity().add(b.getQuantity()),
                                    a.getRecheckRefundQuantity().add(b.getRecheckRefundQuantity()),
                                    a.getRecheckQuantity().add(b.getRecheckQuantity()),
                                    a.getInventory()))
                            .ifPresent(data::add);
                });
        return data;
    }


    /**
     * 订单调用奇门确认接口确认
     *
     * @param outbound
     * @return
     */
    //@Transactional(rollbackFor = {Exception.class})
    public Set<String> deliveryConfirmation(Outbound outbound) {
        String groupCode = outbound.getGroupCode();
        log.info("出库单确认出库， 出库单信息：{}", JSONUtil.toJsonStr(outbound));
        if (OutboundStatusEnum.CANCEL_OUT.getValue().equals(outbound.getStatus())) {
            throw new ServiceException("订单已作废，出库单取消出库");
        }

        // 根据出库单波次号获取所有状态为待出库、有面单号、计划数量=复核数量、未废除的订单号
        log.info("出库确认：获取状态为待出库、出库数量等于计划数量、未取消、有运单号的订单");
        //List<Delivery> deliveries = iDeliveryService.findDeliveryForConfirmDelivery(outbound.getWavePickingCode());


        //获取波次内所有的订单
        List<Delivery> deliveries = iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                .eq(Delivery::getWavePickingCode, outbound.getWavePickingCode())
                .eq(Delivery::getIsCancel, 0)
                .eq(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY)
                .eq(Delivery::getGroupCode,groupCode)
        );
        if (IterUtil.isEmpty(deliveries)) {
            log.info("出库确认：没有复核要确认的信息，跳过确认");
            return null;
        }
        ////待出库列表
        //List<Delivery> unDeliveryList = deliveries.stream().filter(r->StrUtil.equals(r.getStatus(),DeliveryStatusEnum.UN_DELIVERY.getValue())).collect(toList());

        log.info("根据出库单波次号【{}】获取所有状态为待出库、有面单号、计划数量=复核数量、未废除的订单,【{}】", outbound.getWavePickingCode(), deliveries.size());

        Set<String> successCodes = this.deliveryorderConfirm(deliveries);
        return successCodes;


    }


    /**
     * 奇门订单确认
     *
     * @param deliveries
     * @author liuxr
     * @date 2021/5/26 19:19
     */
    public Set<String> deliveryorderConfirm(List<Delivery> deliveries) {
        String groupCode = deliveries.get(NUM_ZERO).getGroupCode();
        // 线程结果集
        Map<String, Future<Boolean>> futureMap = Maps.newHashMap();
        Set<String> failCodes = new HashSet<>();
        Set<String> successCodes = new HashSet<>();
        Set<String> deliveryCodes = deliveries.stream().map(Delivery::getCode).collect(Collectors.toSet());
        List<DeliveryItem> deliveryItemLists = this.iDeliveryItemService.list(new LambdaQueryWrapper<DeliveryItem>()
                .select(DeliveryItem::getDeliveryCode,
                        DeliveryItem::getGoodCode,
                        DeliveryItem::getPlanQuantity,
                        DeliveryItem::getActualPrice,
                        DeliveryItem::getSourceOrderCode,
                        DeliveryItem::getCode)
                .in(DeliveryItem::getDeliveryCode, deliveryCodes)
                .eq(DeliveryItem::getGroupCode,groupCode)
        );

        Map<String, List<DeliveryItem>> deliveryItemMap = deliveryItemLists.stream().collect(Collectors.groupingBy(DeliveryItem::getDeliveryCode));

        StringBuilder failMsg = new StringBuilder("出库确认失败:");
        for (Delivery r : deliveries) {
            if (StrUtil.equals("qimen", r.getSourceType())) {
                //判断非奇门订单是否能缺货发货
                if (StrUtil.isNotBlank(r.getWaybillCode()) && r.getTotalNum().compareTo(r.getRecheckQuantity()) == 0) {
                    Future<Boolean> future = confirmDeliveryUpdateDeliveryStatusService.asyncDeliveryorderConfirm(r, deliveryItemMap.get(r.getCode()));
                    if (future == null) {
                        failCodes.add(r.getCode());
                    } else {
                        futureMap.put(r.getCode(), future);
                    }
                } else {
                    if (StrUtil.isBlank(r.getWaybillCode()) || r.getTotalNum().compareTo(r.getRecheckQuantity()) != 0) {
                        failCodes.add(r.getCode());
                        failMsg.append("订单【").append(r.getCode()).append("】没有面单号或有缺货无法出库<br/>");
                    }
                }
            } else {
                //判断非奇门订单是否能缺货发货
                if (r.getIsDeliveringIfStockout() == 1) {
                    successCodes.add(r.getCode());
                } else {
                    if (r.getTotalNum().compareTo(r.getRecheckQuantity()) == 0) {
                        successCodes.add(r.getCode());
                    } else {
                        failCodes.add(r.getCode());
                        failMsg.append("订单【").append(r.getCode()).append("】有缺货无法出库<br/>");
                    }
                }
            }
        }

        futureMap.forEach((k, v) -> {
            try {
                while (true) {
                    if (v.isDone()) {
                        Boolean status = v.get();
                        if (status) {
                            successCodes.add(k);
                        } else {
                            failCodes.add(k);
                        }
                        break;
                    }
                }
            } catch (ServiceException e) {
                log.error("订单【{}】奇门回调失败【{}】", k, e.getMessage());
                failCodes.add(k);
                failMsg.append("订单【").append(k).append("】奇门回调失败【").append(e.getMessage()).append("】<br/>");

            } catch (Exception e) {
                log.error("订单【{}】奇门回调失败【{}】", k, e.getCause().getMessage());
                failCodes.add(k);
                failMsg.append("订单【").append(k).append("】奇门回调失败【").append(e.getCause().getMessage()).append("】<br/>");
            }
        });
        if (IterUtil.isEmpty(successCodes)) {
            if (IterUtil.isNotEmpty(failCodes)) {
                throw new ServiceException(failMsg.toString());
            }
            throw new ServiceException("出库确认失败，没有复核出库条件的订单信息");
        }

        return successCodes;
    }


    /**
     * 释放容器
     *
     * @param wavePickingCode
     * @author liuxr
     * @date 2021/5/26 19:32
     */
    @Async
    @Transactional(rollbackFor = Exception.class)
    public void releaseContainer(String wavePickingCode,String groupCode) {
        List<String> containerCodes = iDeliveryDistributionService.getContainerCodesForOutCheck(wavePickingCode,groupCode);
        iContainerService.batchReleaseContainerForEmptyInventory(containerCodes,groupCode);
    }

    /**
     * 释放容器
     *
     * @param deliveryCodes
     * @author rfwang
     * @date 2022/5/25 20:32
     */
    @Async
    @Transactional(rollbackFor = Exception.class)
    public void releaseContainer(List<String> deliveryCodes,String groupCode) {
        List<String> containerCodes = iDeliveryDistributionService.getContainerCodesForOutCheck(deliveryCodes,groupCode);
        iContainerService.batchReleaseContainerForEmptyInventory(containerCodes,groupCode);
    }


    /**
     * 更新订单相关信息
     * 订单、订单明细、订单登记
     *
     * @param successCodes
     * @author liuxr
     * @date 2021/5/26 19:34
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateDeliveryInfo(Set<String> successCodes,String groupCode) {
        if (IterUtil.isEmpty(successCodes)) {
            log.info("出库确认更新订单相关信息跳过，订单号为空；");
            return;
        }
        iDeliveryService.update(new LambdaUpdateWrapper<Delivery>()
                .in(Delivery::getCode, successCodes)
                .eq(Delivery::getIsCancel, NUM_ZERO)
                .eq(Delivery::getGroupCode,groupCode)
                .eq(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY.getValue())
                .set(Delivery::getStatus, DeliveryStatusEnum.DELIVERIED.getValue())
                .setSql(" send_num = recheck_quantity"));

        //更新订单明细出库数量
        iDeliveryItemService.update(new LambdaUpdateWrapper<DeliveryItem>()
                .in(DeliveryItem::getDeliveryCode, successCodes)
                .eq(DeliveryItem::getGroupCode,groupCode)
                .setSql(" outbound_quantity = plan_quantity"));

        //更新订单确认明细出库数量
        iDeliveryDistributionService.update(new LambdaUpdateWrapper<DeliveryDistribution>()
                .in(DeliveryDistribution::getDeliveryCode, successCodes)
                .eq(DeliveryDistribution::getGroupCode,groupCode)
                .setSql(" outbound_quantity = distribution_quantity"));
    }


    /**
     * 更新库存
     *
     * @param successCodes
     * @author liuxr
     * @date 2021/5/26 19:38
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateInventory(Set<String> successCodes, String outboundCode,String groupCode) {
        if (IterUtil.isEmpty(successCodes)) {
            log.info("出库确认更新库存跳过，订单号为空；");
            return;
        }
        //获取所有已确认的订单分配信息
        List<DeliveryDistribution> deliveryDistributions = iDeliveryDistributionService.list(new LambdaQueryWrapper<DeliveryDistribution>()
                .select(DeliveryDistribution::getCode,
                        DeliveryDistribution::getDeliveryCode,
                        DeliveryDistribution::getInventoryCode,
                        DeliveryDistribution::getOldInventoryCode,
                        DeliveryDistribution::getDistributionQuantity,
                        DeliveryDistribution::getRecheckRefundQuantity,
                        DeliveryDistribution::getRecheckQuantity,
                        DeliveryDistribution::getPickingQuantity,
                        DeliveryDistribution::getPickStockOutNum)
                .in(DeliveryDistribution::getDeliveryCode, successCodes)
                .eq(DeliveryDistribution::getGroupCode,groupCode));
        //通过分配明细获取所有临时库存标识
        Set<String> tempInventoryCodeSet = deliveryDistributions.parallelStream().map(DeliveryDistribution::getInventoryCode).collect(Collectors.toSet());

        //通过临时库存、原始库存获取所有库存信息
        List<Inventory> inventories = iInventoryService.list(new LambdaQueryWrapper<Inventory>().in(Inventory::getCode, tempInventoryCodeSet));

        //将库存信息通过库存code转成map
        Map<String, Inventory> inventoryMap = inventories.stream().collect(Collectors.toMap(Inventory::getCode, r -> r));

        List<InventoryOperationDto> subtract = deliveryDistributions.parallelStream().map(d -> {
            InventoryOperationDto dto = new InventoryOperationDto();
            dto.setUuid(IdUtil.simpleUUID());
            dto.setQuantity(d.getDistributionQuantity());
            dto.setInventoryCode(d.getInventoryCode());
            dto.setInventory(inventoryMap.get(d.getInventoryCode()));
            return dto;
        }).collect(toList());

        List<InventoryRecord> inventoryRecords = new ArrayList<>();
        //创建减库存操作记录
        subtract.forEach(r -> {
            //临时库存减库存操作记录
            InventoryRecord record = new InventoryRecord();
            BeanUtil.copyProperties(r.getInventory(), record);
            record.setCode(IdUtil.simpleUUID());
            record.setGroupCode(groupCode);
            record.setInventoryCode(r.getInventoryCode());
            record.setSourceCode(outboundCode);
            record.setNum(r.getQuantity().negate());
            record.setType(InventoryRecordTypeEnum.OUTBOUND.getValue());
            inventoryRecords.add(record);
        });

        //批量减少临时库存
        inventoryOperationService.subtractBatch(subtract);

        //批量插入操作记录
        iInventoryRecordService.saveBatch(inventoryRecords);
    }


    /*************************************订单、出库单一对一逻辑BEGIN************************************************/
    /**
     * 批量出库处理取消订单
     *
     * @param wavePickingCode 波次号
     */
    @Transactional(rollbackFor = Exception.class)
    public  Set<String> handleDeliveryCancelByWavePickingCode(String wavePickingCode, String groupCode) {
        log.info("根据波次号处理取消订单，波次号：{}",wavePickingCode);
        //获取所有取消的订单
        List<Delivery> deliveryList = iDeliveryService.list(Wrappers.lambdaQuery(Delivery.class)
                .select(Delivery::getCode, Delivery::getStatus, Delivery::getWarehouseCode)
                .eq(Delivery::getWavePickingCode, wavePickingCode)
                .eq(Delivery::getGroupCode, groupCode)
                .eq(Delivery::getIsCancel, NUM_ONE));
        log.info("根据波次号处理取消订单，获取所有取消状态订单，波次号：{}，订单：{}",wavePickingCode,JSONUtil.toJsonStr(deliveryList));
        //汇总作废单的总拣货数量、总退拣数量、总复核数量
        if (IterUtil.isNotEmpty(deliveryList)) {

            Set<String> cancelCodes = deliveryList.stream().map(Delivery::getCode).collect(Collectors.toSet());
            //获取取消还未更新成作废的订单
            List<Delivery> tempList = deliveryList.stream().filter(r -> !StrUtil.equals(r.getStatus(), DeliveryStatusEnum.VOID.getValue())).collect(toList());
            log.info("根据波次号处理取消订单，获取已取消未作废的订单，波次号：{}，订单：{}",wavePickingCode,JSONUtil.toJsonStr(tempList));
            if (IterUtil.isNotEmpty(tempList)) {
                Delivery first = tempList.get(0);
                //获取所有取消未作废的订单号
                List<String> deliveryCodes = tempList.stream().map(Delivery::getCode).collect(toList());
                //根据订单号获取所有分配明细
                List<DeliveryDistribution> deliveryDistributions = iDeliveryDistributionService.list(Wrappers.lambdaQuery(DeliveryDistribution.class)
                        .select(DeliveryDistribution::getCode,
                                DeliveryDistribution::getDeliveryCode,
                                DeliveryDistribution::getInventoryCode,
                                DeliveryDistribution::getOldInventoryCode,
                                DeliveryDistribution::getDistributionQuantity,
                                DeliveryDistribution::getRecheckRefundQuantity,
                                DeliveryDistribution::getRecheckQuantity,
                                DeliveryDistribution::getPickingQuantity,
                                DeliveryDistribution::getPickStockOutNum)
                        .in(DeliveryDistribution::getDeliveryCode, deliveryCodes)
                        .eq(DeliveryDistribution::getGroupCode, groupCode));
                //将所有的分配明细按订单号分组
                Map<String, List<DeliveryDistribution>> distList = deliveryDistributions.stream().collect(Collectors.groupingBy(DeliveryDistribution::getDeliveryCode));
                log.info("根据波次号处理取消订单，获取已取消未作废的订单分配明细，订单号：{}，波次号：{}，分配明细：{}",deliveryCodes,wavePickingCode,JSONUtil.toJsonStr(distList));


                //通过分配明细获取所有临时库存标识
                Set<String> tempInventoryCodeSet = deliveryDistributions.stream().map(DeliveryDistribution::getInventoryCode).collect(Collectors.toSet());
                log.info("根据波次号处理取消订单，获取已取消未作废的订单分配明细临时库存标识，订单号：{}，波次号：{}，临时库存标识：{}",deliveryCodes,wavePickingCode,JSONUtil.toJsonStr(tempInventoryCodeSet));
                //通过临时库存、原始库存获取所有库存信息
                List<Inventory> inventories = iInventoryService.list( Wrappers.lambdaQuery(Inventory.class).in(Inventory::getCode, tempInventoryCodeSet));
                //将库存信息通过库存code转成map
                Map<String, Inventory> inventoryMap = inventories.stream().collect(Collectors.toMap(Inventory::getCode, r -> r));
                log.info("根据波次号处理取消订单，获取已取消未作废的订单分配明细临时库存，订单号：{}，波次号：{}，临时库存：{}",deliveryCodes,wavePickingCode,JSONUtil.toJsonStr(inventoryMap));

                //遍历取消订单集合获取订单要减少的临时库存、要增加的原始库存、操作数量
                Map<String, Map<String, Object>> map = tempList.stream()
                        .collect(Collectors.toMap(Delivery::getCode, delivery -> {

                            //根据订单号获取订单的分配明细
                            List<DeliveryDistribution> distributions = distList.get(delivery.getCode());
                            Map<String, List<DeliveryDistribution>> listMap = distributions.parallelStream()
                                    .collect(Collectors.groupingBy(DeliveryDistribution::getInventoryCode));
                            //将分配明细按照临时库存标识分组，计算临时库存需要减少的数量
                            //返回 Map<临时库存标识,合计分配数量> 要减少库存的临时库存及数量
                            List<InventoryOperationDto> pickingQuantitys = listMap.entrySet().parallelStream()
                                    .map(m -> {
                                        InventoryOperationDto dto = new InventoryOperationDto();
                                        dto.setUuid(IdUtil.simpleUUID());
                                        dto.setInventoryCode(m.getKey());
                                        dto.setInventory(inventoryMap.get(m.getKey()));
                                        dto.setQuantity(m.getValue()
                                                .stream()
                                                .map(DeliveryDistribution::getPickingQuantity)
                                                .reduce(BigDecimal.ZERO, BigDecimal::add));
                                        dto.setRecheckRefundQuantity(m.getValue()
                                                .stream()
                                                .map(DeliveryDistribution::getRecheckRefundQuantity)
                                                .reduce(BigDecimal.ZERO, BigDecimal::add));
                                        dto.setRecheckQuantity((dto.getQuantity()).subtract(dto.getRecheckRefundQuantity()));
                                        return dto;

                                    })
                                    .collect(Collectors.toList());
                            Map<String, Object> cancelQuantityMap = new HashMap<>();
                            cancelQuantityMap.put("deliveryCode", delivery);
                            cancelQuantityMap.put("pickingQuantitys", pickingQuantitys);
                            return cancelQuantityMap;
                        }));


                //要减少库存的临时库存总集合
                List<InventoryOperationDto> subtract = new ArrayList<>();
                //所有库存操作记录集合
                List<InventoryRecord> inventoryRecords = new ArrayList<>();

                //退减库存
                List<Inventory> reInventorys = new ArrayList<>();
                // 获取退减临时库位
                Location reLocation = iLocationService.getOne(new LambdaQueryWrapper<Location>().select(Location::getCode)
                        .eq(Location::getWarehouseCode, first.getWarehouseCode())
                        .eq(Location::getSerialNumber, TempLocationTypeEnum.RE));
                if (null == reLocation) {
                    log.error("仓库【{}】缺少退减库位", first.getWarehouseCode());
                    throw new ServiceException("拣货确认失败，仓库缺少退减库位");
                }
                //遍历合并所有要处理的信息
                map.forEach((k, v) -> {
                    List<InventoryOperationDto> pickingQuantitys = (List<InventoryOperationDto>) v.get("pickingQuantitys");
                    //合并所有要减少库存的临时库存
                    subtract.addAll(pickingQuantitys);

                    //创建减库存操作记录
                    pickingQuantitys.forEach(r -> {
                        //临时库存减库存操作记录
                        InventoryRecord record = new InventoryRecord();
                        BeanUtil.copyProperties(r.getInventory(), record);
                        record.setCode(IdUtil.simpleUUID());
                        record.setGroupCode(groupCode);
                        record.setInventoryCode(r.getInventoryCode());
                        record.setSourceCode(k);
                        record.setNum(r.getQuantity().negate());
                        record.setType(InventoryRecordTypeEnum.OUTBOUND.getValue());
                        record.setRemarks("订单："+k+"取消，分配的临时库存减少库存");
                        inventoryRecords.add(record);

                        //退减库存信息
                        Inventory inventory = new Inventory();
                        BeanUtil.copyProperties(r.getInventory(), inventory);
                        inventory.setCode(IdUtil.simpleUUID());
                        inventory.setUseQuantity(BigDecimal.ZERO);
                        inventory.setOldInventoryCode(r.getInventory().getCode());
                        inventory.setTempCode(StrUtil.EMPTY);
                        inventory.setTempItemCode(StrUtil.EMPTY);
                        inventory.setTempRegisterCode(StrUtil.EMPTY);
                        inventory.setLocation(reLocation.getCode());
                        inventory.setQuantity(r.getQuantity());
                        inventory.setRemarks("订单："+k+"取消，分配库存到退拣库位");
                        // 抹去容器号等信息
                        inventory.setContainerBarCode(StrUtil.EMPTY);
                        reInventorys.add(inventory);

                        //退减库存擦插入操作记录
                        InventoryRecord reRecord = new InventoryRecord();
                        BeanUtil.copyProperties(inventory, reRecord);
                        reRecord.setCode(IdUtil.simpleUUID());
                        reRecord.setGroupCode(groupCode);
                        reRecord.setInventoryCode(inventory.getCode());
                        reRecord.setSourceCode(k);
                        reRecord.setNum(inventory.getQuantity());
                        reRecord.setRemarks("订单："+k+"取消，已拣商品存放到退拣库位");
                        reRecord.setType(InventoryRecordTypeEnum.OUTBOUND.getValue());
                        inventoryRecords.add(reRecord);
                    });


                });


                // 接收处理后的数据
                List<InventoryOperationDto> subtractData = getInventoryOperationDtos(subtract);

                //减少临时库存
                inventoryOperationService.subtractBatch(subtractData);


                List<Inventory> reInventoryDatas = new ArrayList<>();
                // 数据分组统计处理
                reInventorys.parallelStream()
                        .collect(Collectors.groupingBy(Inventory::getOldInventoryCode, Collectors.toList()))
                        .forEach((inventoryCode, dtos) -> {
                            dtos.stream()
                                    .reduce((a, b) -> {
                                        Inventory inventory = new Inventory();
                                        BeanUtil.copyProperties(a, inventory);
                                        inventory.setCode(IdUtil.simpleUUID());
                                        inventory.setQuantity(a.getQuantity().add(b.getQuantity()));
                                        return inventory;
                                    })
                                    .ifPresent(reInventoryDatas::add);
                        });

                //批量插入退减库存 TODO 未合并
                iInventoryService.saveBatch(reInventoryDatas);

                //批量插入操作记录
                iInventoryRecordService.saveBatch(inventoryRecords);


                // 更新订单的状态，改为作废
                iDeliveryService.update(new UpdateWrapper<Delivery>().lambda()
                        .in(Delivery::getCode, deliveryCodes)
                        .eq(Delivery::getIsCancel, NUM_ONE)
                        .eq(Delivery::getGroupCode, groupCode)
                        .set(Delivery::getStatus, DeliveryStatusEnum.VOID.getValue())
//                        .set(Delivery::getWaybillCode, "")
                );
                //自动取消面单号，放开上边和下边注释即可
                //tmsWaybillUpdateService.asyncTmsWaybillDiscard(tempList);
            }
            return cancelCodes;
        }
        return null;

    }



    /**
     * 拣货跳过复核，出库自动复核
     *
     * @param wavePickingCode 波次单号
     */
    @Transactional(rollbackFor = Exception.class)
    public void autoReview(String wavePickingCode,String groupCode) {
        log.info("波次拣货自动复核【step1】。更新订单、订单明细、订单分配明细复核数量及复核完成状态");
        iDeliveryService.autoUpdateStatusAndRecheckQuantity(wavePickingCode,groupCode);
        log.info("波次拣货自动复核【step2】。更新出库单、出库明细、出库复核确认、复核数量及复核完成状态");
        outboundMapper.autoUpdateStatusAndRecheckQuantity(wavePickingCode,groupCode);
    }



    /**
     * 拣货跳过复核，出库自动确认
     *
     * @param wavePickingCode 波次单号
     * @param delvieryCode 订单号
     * isReview 是否跳过复核，0 跳过，1 不跳过
     * type：0：通过出库单号查询出库单进行出库，相当于一个魔法值 （产生原因：由于自动拣货失败后不需要复核自动出库，但是用户余额不足，无法让一个
     *                     波次的所有订单自动出库，然后用户就从出库单列表进行单独出库，这时候从出库单列表出库的时候是通过波次号查询的出库单，所以还是出库失败，
     *                     故加了一个魔法值，判断只要从出库单列表进行出库的都通过出库单号进行查询出库单）
     */
    public void autoOutConfirmation(String wavePickingCode,String delvieryCode,Integer isReview,Integer type,String groupCode) {
        //获取波次内所有的订单
        List<Delivery> deliveries ;
        if (isReview == 0) {
            deliveries = iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                    .eq(Delivery::getWavePickingCode, wavePickingCode)
                    .eq(Delivery::getGroupCode,groupCode)
                    .eq(Delivery::getIsCancel, NUM_ZERO)
                    .eq(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY));
        } else {
            //需要复核的，如果入参deliveryCode为空，则是批量复核，否则是按单复核
            if (StrUtil.isNotEmpty(delvieryCode)) {
                deliveries = iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                        .eq(Delivery::getGroupCode,groupCode)
                        .eq(Delivery::getCode, delvieryCode)
                        .eq(Delivery::getIsCancel, NUM_ZERO)
                        .eq(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY));
            } else {
                deliveries = iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                        .eq(Delivery::getWavePickingCode, wavePickingCode)
                        .eq(Delivery::getGroupCode,groupCode)
                        .eq(Delivery::getIsCancel, NUM_ZERO)
                        .eq(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY));
            }
        }

        if (IterUtil.isEmpty(deliveries)) {
            log.info("出库确认：没有出库要确认的信息，跳过确认");
            throw new ServiceException("出库确认：没有出库要确认的信息，跳过确认！");
//            return;
        }

        if(type == 0){
            deliveries = iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                    .eq(Delivery::getGroupCode,groupCode)
                    .eq(Delivery::getCode, delvieryCode)
                    .eq(Delivery::getIsCancel, NUM_ZERO)
                    .eq(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY));
        }
        log.info("波次【{}】待自动出库确认的订单信息：{}",wavePickingCode,JSONUtil.toJsonStr(deliveries));

        List<Delivery> qimenDeliverieList = deliveries.stream().
                filter(r ->
                        StrUtil.equals("qimen", r.getSourceType()) && (StrUtil.isNotBlank(r.getWaybillCode()) && r.getTotalNum().compareTo(r.getRecheckQuantity()) == 0)
                ).collect(Collectors.toList());

        log.info("波次【{}】待自动出库确认的奇门订单信息：{}",wavePickingCode,JSONUtil.toJsonStr(qimenDeliverieList));
        List<Delivery> noQimenDeliverieList = deliveries.stream().
                filter(r ->
                        !StrUtil.equals("qimen", r.getSourceType()) && ((r.getIsDeliveringIfStockout() == 1) || (r.getTotalNum().compareTo(r.getRecheckQuantity()) == 0))
                ).collect(Collectors.toList());

        log.info("波次【{}】待自动出库确认的非奇门订单信息：{}",wavePickingCode,JSONUtil.toJsonStr(noQimenDeliverieList));
        //判断账户余额数量
        int qimenSize = qimenDeliverieList.size();
        int noqimensize = noQimenDeliverieList.size();
        this.accountBalance(qimenSize + noqimensize,groupCode);


        //奇门订单需要调用奇门接口确认
        if (IterUtil.isNotEmpty(qimenDeliverieList)) {
            //复核调用奇门接口的订单
            Set<String> qimenSuccessCodes = qimenDeliverieList.stream().map(Delivery::getCode).collect(Collectors.toSet());
            List<DeliveryItem> deliveryItemLists = this.iDeliveryItemService.list(new LambdaQueryWrapper<DeliveryItem>()
                    .select(DeliveryItem::getDeliveryCode,
                            DeliveryItem::getGoodCode,
                            DeliveryItem::getPlanQuantity,
                            DeliveryItem::getActualPrice,
                            DeliveryItem::getSourceOrderCode,
                            DeliveryItem::getCode)
                    .in(DeliveryItem::getDeliveryCode, qimenSuccessCodes)
                    .eq(DeliveryItem::getGroupCode,groupCode));
            Map<String, List<DeliveryItem>> deliveryItemMap = deliveryItemLists.stream().collect(Collectors.groupingBy(DeliveryItem::getDeliveryCode));
            for (Delivery r : qimenDeliverieList) {
                log.info("奇门订单【{}】,波次拣货确认，不需要复核操作时，自动更新出库数量及状态",r.getCode());
                confirmDeliveryUpdateDeliveryStatusService.asyncAutoConfirm(r, deliveryItemMap.get(r.getCode()),groupCode);
            }
        }

        //非奇门订单
        if (IterUtil.isNotEmpty(noQimenDeliverieList)) {
            //手动创建或导入订单符合出库确认的订单
            Set<String> noQimenSuccessCodes = noQimenDeliverieList.stream().
                    map(Delivery::getCode).collect(Collectors.toSet());
            //根据订单号查询出库单
            List<String> outboundCodes = iOutboundService.getOutboundCodesByDeliveryCodes(noQimenSuccessCodes,groupCode);
            //更新订单发货数量及状态
            iDeliveryService.autoUpdateStatusAndSendQuantity(noQimenSuccessCodes,groupCode);
            log.info("非奇门订单【{}】,更新订单发货数量及状态",noQimenSuccessCodes);
            //更新出库单发货数量
            outboundMapper.autoNoQimenUpdateStatusAndSendQuantity(outboundCodes,groupCode);
            log.info("非奇门订单【{}】,更新出库单发货数量及状态",noQimenSuccessCodes);

            //获取所有已确认的订单分配信息
            List<DeliveryDistribution> deliveryDistributions = iDeliveryDistributionService.list(new LambdaQueryWrapper<DeliveryDistribution>()
                    .select(DeliveryDistribution::getCode,
                            DeliveryDistribution::getDeliveryCode,
                            DeliveryDistribution::getInventoryCode,
                            DeliveryDistribution::getOldInventoryCode,
                            DeliveryDistribution::getDistributionQuantity,
                            DeliveryDistribution::getRecheckRefundQuantity,
                            DeliveryDistribution::getRecheckQuantity,
                            DeliveryDistribution::getPickingQuantity,
                            DeliveryDistribution::getGroupCode,
                            DeliveryDistribution::getPickStockOutNum)
                    .in(DeliveryDistribution::getDeliveryCode, noQimenSuccessCodes)
                    .eq(DeliveryDistribution::getGroupCode,groupCode));
            //更新临时库存
            inventoryOperationService.autoUpdateInventory(deliveryDistributions);
            log.info("非奇门订单【{}】,更新临时库存",noQimenSuccessCodes);
            //扣费
            for (Delivery r : noQimenDeliverieList) {
                Outbound outbound = iOutboundService.getOne(new QueryWrapper<Outbound>().lambda()
                        .eq(Outbound::getGroupCode,groupCode)
                        .eq(Outbound::getDeliveryCode, r.getCode())
                        .eq(Outbound::getDeleted, "0"));
                outbound.setWaybillCode(r.getWaybillCode());//面单号
                if(outbound.getType().equalsIgnoreCase("0")){ //只有销售出库扣费
                    deductionByBill(outbound);
                }
                //出库完成后的操作
                List<OutboundItem> outboundItems = iOutboundItemService.list(new LambdaQueryWrapper<OutboundItem>()
                        .eq(OutboundItem::getOutboundCode, outbound.getCode())
                        .eq(OutboundItem::getGroupCode,groupCode));
                // 发货单后续操作，如更新oms单据，更新领用、破损、销售退单信息等
                iDeliveryService.afterOutbound(outbound, outboundItems);
                // 使用oms 回写数据
                omsOutboundService.saveByWmsOutbound(outbound, outboundItems);
            }
        }
        //提示语
        if(IterUtil.isEmpty(noQimenDeliverieList) && IterUtil.isEmpty(qimenDeliverieList)){
             qimenDeliverieList = deliveries.stream().
                    filter(r ->
                            StrUtil.equals("qimen", r.getSourceType()) && (StrUtil.isNotBlank(r.getWaybillCode()))
                    ).collect(Collectors.toList());
            noQimenDeliverieList = deliveries.stream().
                    filter(r ->
                            !StrUtil.equals("qimen", r.getSourceType())
                    ).collect(Collectors.toList());
             if(IterUtil.isNotEmpty(qimenDeliverieList) || IterUtil.isNotEmpty(noQimenDeliverieList)){
                 throw new ServiceException("存在缺货不能出库！");
             }
             qimenDeliverieList = deliveries.stream().
                    filter(r ->
                            StrUtil.equals("qimen", r.getSourceType()) && (r.getTotalNum().compareTo(r.getRecheckQuantity()) == 0)
                    ).collect(Collectors.toList());
            if(IterUtil.isNotEmpty(qimenDeliverieList)){
                throw new ServiceException("面单号不存在不能出库！");
            }
            throw new ServiceException("存在缺货不能出库或面单号为空！");
        }
    }

    /**
     * 批量复核出库，出库自动确认
     *
     * @param deliveryCodes 订单集合
     * @param groupCode 组织机构
     */
    public void autoOutConfirmationByDeliveryCodes(List<String> deliveryCodes,String groupCode) {
        //获取波次内所有的订单
        List<Delivery> deliveries = iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                .in(Delivery::getCode, deliveryCodes)
                .eq(Delivery::getGroupCode,groupCode)
                .eq(Delivery::getIsCancel, NUM_ZERO)
                .eq(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY));

        if (IterUtil.isEmpty(deliveries)) {
            log.info("出库确认：没有出库要确认的信息，跳过确认");
            throw new ServiceException("出库确认：没有出库要确认的信息，跳过确认！");
        }

        log.info("订单集合【{}】待自动出库确认的订单信息：{}",deliveryCodes,JSONUtil.toJsonStr(deliveries));

        List<Delivery> qimenDeliverieList = deliveries.stream().
                filter(r ->
                        StrUtil.equals("qimen", r.getSourceType()) && (StrUtil.isNotBlank(r.getWaybillCode()) && r.getTotalNum().compareTo(r.getRecheckQuantity()) == 0)
                ).collect(Collectors.toList());

        log.info("订单集合【{}】待自动出库确认的奇门订单信息：{}",deliveryCodes,JSONUtil.toJsonStr(qimenDeliverieList));
        List<Delivery> noQimenDeliverieList = deliveries.stream().
                filter(r ->
                        !StrUtil.equals("qimen", r.getSourceType()) && ((r.getIsDeliveringIfStockout() == 1) || (r.getTotalNum().compareTo(r.getRecheckQuantity()) == 0))
                ).collect(Collectors.toList());

        log.info("订单集合【{}】待自动出库确认的非奇门订单信息：{}",deliveryCodes,JSONUtil.toJsonStr(noQimenDeliverieList));
        //判断账户余额数量
        int qimenSize = qimenDeliverieList.size();
        int noqimensize = noQimenDeliverieList.size();
        this.accountBalance(qimenSize + noqimensize,groupCode);


        //奇门订单需要调用奇门接口确认
        if (IterUtil.isNotEmpty(qimenDeliverieList)) {
            //复核调用奇门接口的订单
            Set<String> qimenSuccessCodes = qimenDeliverieList.stream().map(Delivery::getCode).collect(Collectors.toSet());
            List<DeliveryItem> deliveryItemLists = this.iDeliveryItemService.list(new LambdaQueryWrapper<DeliveryItem>()
                    .select(DeliveryItem::getDeliveryCode,
                            DeliveryItem::getGoodCode,
                            DeliveryItem::getPlanQuantity,
                            DeliveryItem::getActualPrice,
                            DeliveryItem::getSourceOrderCode,
                            DeliveryItem::getCode)
                    .in(DeliveryItem::getDeliveryCode, qimenSuccessCodes)
                    .eq(DeliveryItem::getGroupCode,groupCode));
            Map<String, List<DeliveryItem>> deliveryItemMap = deliveryItemLists.stream().collect(Collectors.groupingBy(DeliveryItem::getDeliveryCode));
            for (Delivery r : qimenDeliverieList) {
                log.info("奇门订单【{}】,波次拣货确认，不需要复核操作时，自动更新出库数量及状态",r.getCode());
                confirmDeliveryUpdateDeliveryStatusService.asyncAutoConfirm(r, deliveryItemMap.get(r.getCode()),groupCode);
            }
        }

        //非奇门订单
        if (IterUtil.isNotEmpty(noQimenDeliverieList)) {
            //手动创建或导入订单符合出库确认的订单
            Set<String> noQimenSuccessCodes = noQimenDeliverieList.stream().
                    map(Delivery::getCode).collect(Collectors.toSet());
            //根据订单号查询出库单
            List<String> outboundCodes = iOutboundService.getOutboundCodesByDeliveryCodes(noQimenSuccessCodes,groupCode);
            //更新订单发货数量及状态
            iDeliveryService.autoUpdateStatusAndSendQuantity(noQimenSuccessCodes,groupCode);
            log.info("非奇门订单【{}】,更新订单发货数量及状态",noQimenSuccessCodes);
            //更新出库单发货数量
            outboundMapper.autoNoQimenUpdateStatusAndSendQuantity(outboundCodes,groupCode);
            log.info("非奇门订单【{}】,更新出库单发货数量及状态",noQimenSuccessCodes);

            //获取所有已确认的订单分配信息
            List<DeliveryDistribution> deliveryDistributions = iDeliveryDistributionService.list(new LambdaQueryWrapper<DeliveryDistribution>()
                    .select(DeliveryDistribution::getCode,
                            DeliveryDistribution::getDeliveryCode,
                            DeliveryDistribution::getInventoryCode,
                            DeliveryDistribution::getOldInventoryCode,
                            DeliveryDistribution::getDistributionQuantity,
                            DeliveryDistribution::getRecheckRefundQuantity,
                            DeliveryDistribution::getRecheckQuantity,
                            DeliveryDistribution::getPickingQuantity,
                            DeliveryDistribution::getGroupCode,
                            DeliveryDistribution::getPickStockOutNum)
                    .in(DeliveryDistribution::getDeliveryCode, noQimenSuccessCodes)
                    .eq(DeliveryDistribution::getGroupCode,groupCode));
            //更新临时库存
            inventoryOperationService.autoUpdateInventory(deliveryDistributions);
            log.info("非奇门订单【{}】,更新临时库存",noQimenSuccessCodes);
            //扣费
            for (Delivery r : noQimenDeliverieList) {
                Outbound outbound = iOutboundService.getOne(new QueryWrapper<Outbound>().lambda()
                        .eq(Outbound::getGroupCode,groupCode)
                        .eq(Outbound::getDeliveryCode, r.getCode())
                        .eq(Outbound::getDeleted, "0"));
                outbound.setWaybillCode(r.getWaybillCode());//面单号
                if(outbound.getType().equalsIgnoreCase("0")){ //只有销售出库扣费
                    deductionByBill(outbound);
                }
                //出库完成后的操作
                List<OutboundItem> outboundItems = iOutboundItemService.list(new LambdaQueryWrapper<OutboundItem>()
                        .eq(OutboundItem::getOutboundCode, outbound.getCode())
                        .eq(OutboundItem::getGroupCode,groupCode));
                // 发货单后续操作，如更新oms单据，更新领用、破损、销售退单信息等
                iDeliveryService.afterOutbound(outbound, outboundItems);
                // 使用oms 回写数据
                omsOutboundService.saveByWmsOutbound(outbound, outboundItems);
            }
        }
        //提示语
        if(IterUtil.isEmpty(noQimenDeliverieList) && IterUtil.isEmpty(qimenDeliverieList)){
            qimenDeliverieList = deliveries.stream().
                    filter(r ->
                            StrUtil.equals("qimen", r.getSourceType()) && (StrUtil.isNotBlank(r.getWaybillCode()))
                    ).collect(Collectors.toList());
            noQimenDeliverieList = deliveries.stream().
                    filter(r ->
                            !StrUtil.equals("qimen", r.getSourceType())
                    ).collect(Collectors.toList());
            if(IterUtil.isNotEmpty(qimenDeliverieList) || IterUtil.isNotEmpty(noQimenDeliverieList)){
                throw new ServiceException("存在缺货不能出库！");
            }
            qimenDeliverieList = deliveries.stream().
                    filter(r ->
                            StrUtil.equals("qimen", r.getSourceType()) && (r.getTotalNum().compareTo(r.getRecheckQuantity()) == 0)
                    ).collect(Collectors.toList());
            if(IterUtil.isNotEmpty(qimenDeliverieList)){
                throw new ServiceException("面单号不存在不能出库！");
            }
            throw new ServiceException("存在缺货不能出库或面单号为空！");
        }
    }

    /**
     * 爆品复核出库自动确认，与autoOutConfirmation的唯一区别是进行订单状态回显万里牛的时候没有用异步方法
     *
     * @param wavePickingCode 波次单号
     * @param delvieryCode 订单号
     * isReview 是否跳过复核，0 跳过，1 不跳过
     * type：0：通过出库单号查询出库单进行出库，相当于一个魔法值 （产生原因：由于自动拣货失败后不需要复核自动出库，但是用户余额不足，无法让一个
     *                     波次的所有订单自动出库，然后用户就从出库单列表进行单独出库，这时候从出库单列表出库的时候是通过波次号查询的出库单，所以还是出库失败，
     *                     故加了一个魔法值，判断只要从出库单列表进行出库的都通过出库单号进行查询出库单）
     */
    public void autoOutConfirmationForHotReview(String wavePickingCode,String delvieryCode,Integer isReview,Integer type) {
        String groupCode = UserUtil.getBranchCode();
        //获取波次内所有的订单
        List<Delivery> deliveries ;
        if (isReview == 0) {
            deliveries = iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                    .eq(Delivery::getGroupCode,groupCode)
                    .eq(Delivery::getWavePickingCode, wavePickingCode)
                    .eq(Delivery::getIsCancel, NUM_ZERO)
                    .eq(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY));
        } else {
            //需要复核的，如果入参deliveryCode为空，则是批量复核，否则是按单复核
            if (StrUtil.isNotEmpty(delvieryCode)) {
                deliveries = iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                        .eq(Delivery::getGroupCode,groupCode)
                        .eq(Delivery::getCode, delvieryCode)
                        .eq(Delivery::getIsCancel, NUM_ZERO)
                        .eq(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY));
            } else {
                deliveries = iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                        .eq(Delivery::getGroupCode,groupCode)
                        .eq(Delivery::getWavePickingCode, wavePickingCode)
                        .eq(Delivery::getIsCancel, NUM_ZERO)
                        .eq(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY));
            }
        }

        if (IterUtil.isEmpty(deliveries)) {
            log.info("出库确认：没有出库要确认的信息，跳过确认");
            throw new ServiceException("出库确认：没有出库要确认的信息，跳过确认！");
//            return;
        }

        if(type == 0){
            deliveries = iDeliveryService.list(new LambdaQueryWrapper<Delivery>()
                    .eq(Delivery::getGroupCode,groupCode)
                    .eq(Delivery::getCode, delvieryCode)
                    .eq(Delivery::getIsCancel, NUM_ZERO)
                    .eq(Delivery::getStatus, DeliveryStatusEnum.UN_DELIVERY));
        }

        List<Delivery> qimenDeliverieList = deliveries.stream().
                filter(r ->
                        StrUtil.equals("qimen", r.getSourceType()) && (StrUtil.isNotBlank(r.getWaybillCode()) && r.getTotalNum().compareTo(r.getRecheckQuantity()) == 0)
                ).collect(Collectors.toList());
        List<Delivery> noQimenDeliverieList = deliveries.stream().
                filter(r ->
                        !StrUtil.equals("qimen", r.getSourceType()) && ((r.getIsDeliveringIfStockout() == 1) || (r.getTotalNum().compareTo(r.getRecheckQuantity()) == 0))
                ).collect(Collectors.toList());

        //判断账户余额数量
        int qimenSize = qimenDeliverieList.size();
        int noqimensize = noQimenDeliverieList.size();
        this.accountBalance(qimenSize + noqimensize,groupCode);


        //奇门订单需要调用奇门接口确认
        if (IterUtil.isNotEmpty(qimenDeliverieList)) {
            //复核调用奇门接口的订单
            Set<String> qimenSuccessCodes = qimenDeliverieList.stream().map(Delivery::getCode).collect(Collectors.toSet());
            List<DeliveryItem> deliveryItemLists = this.iDeliveryItemService.list(new LambdaQueryWrapper<DeliveryItem>()
                    .select(DeliveryItem::getDeliveryCode,
                            DeliveryItem::getGoodCode,
                            DeliveryItem::getPlanQuantity,
                            DeliveryItem::getActualPrice,
                            DeliveryItem::getSourceOrderCode,
                            DeliveryItem::getCode)
                    .in(DeliveryItem::getDeliveryCode, qimenSuccessCodes)
                    .eq(DeliveryItem::getGroupCode,groupCode));
            Map<String, List<DeliveryItem>> deliveryItemMap = deliveryItemLists.stream().collect(Collectors.groupingBy(DeliveryItem::getDeliveryCode));
            for (Delivery r : qimenDeliverieList) {
                //没有用异步，只是为了让爆品查询的时候出库单的状态显现准确
                confirmDeliveryUpdateDeliveryStatusService.autoConfirm(r, deliveryItemMap.get(r.getCode()),groupCode);
            }
        }

        //非奇门订单
        if (IterUtil.isNotEmpty(noQimenDeliverieList)) {
            //手动创建或导入订单符合出库确认的订单
            Set<String> noQimenSuccessCodes = noQimenDeliverieList.stream().
                    map(Delivery::getCode).collect(Collectors.toSet());
            //根据订单号查询出库单
            List<String> outboundCodes = iOutboundService.getOutboundCodesByDeliveryCodes(noQimenSuccessCodes,groupCode);
            //更新订单发货数量及状态
            iDeliveryService.autoUpdateStatusAndSendQuantity(noQimenSuccessCodes,groupCode);
            //更新出库单发货数量
            outboundMapper.autoNoQimenUpdateStatusAndSendQuantity(outboundCodes,groupCode);

            //获取所有已确认的订单分配信息
            List<DeliveryDistribution> deliveryDistributions = iDeliveryDistributionService.list(new LambdaQueryWrapper<DeliveryDistribution>()
                    .select(DeliveryDistribution::getCode,
                            DeliveryDistribution::getDeliveryCode,
                            DeliveryDistribution::getInventoryCode,
                            DeliveryDistribution::getOldInventoryCode,
                            DeliveryDistribution::getDistributionQuantity,
                            DeliveryDistribution::getRecheckRefundQuantity,
                            DeliveryDistribution::getRecheckQuantity,
                            DeliveryDistribution::getPickingQuantity,
                            DeliveryDistribution::getGroupCode,
                            DeliveryDistribution::getPickStockOutNum)
                    .in(DeliveryDistribution::getDeliveryCode, noQimenSuccessCodes)
                    .eq(DeliveryDistribution::getGroupCode,groupCode));
            //更新临时库存
            inventoryOperationService.autoUpdateInventory(deliveryDistributions);
            //扣费
            for (Delivery r : noQimenDeliverieList) {
                Outbound outbound = iOutboundService.getOne(new QueryWrapper<Outbound>().lambda()
                        .eq(Outbound::getGroupCode,groupCode)
                        .eq(Outbound::getDeliveryCode, r.getCode())
                        .eq(Outbound::getDeleted, "0"));
                outbound.setWaybillCode(r.getWaybillCode());//面单号
                if(outbound.getType().equalsIgnoreCase("0")){ //只有销售出库扣费
                    deductionByBill(outbound);
                }
            }
        }
        //提示语
        if(IterUtil.isEmpty(noQimenDeliverieList) && IterUtil.isEmpty(qimenDeliverieList)){
            qimenDeliverieList = deliveries.stream().
                    filter(r ->
                            StrUtil.equals("qimen", r.getSourceType()) && (StrUtil.isNotBlank(r.getWaybillCode()))
                    ).collect(Collectors.toList());
            noQimenDeliverieList = deliveries.stream().
                    filter(r ->
                            !StrUtil.equals("qimen", r.getSourceType())
                    ).collect(Collectors.toList());
            if(IterUtil.isNotEmpty(qimenDeliverieList) || IterUtil.isNotEmpty(noQimenDeliverieList)){
                throw new ServiceException("存在缺货不能出库！");
            }
            qimenDeliverieList = deliveries.stream().
                    filter(r ->
                            StrUtil.equals("qimen", r.getSourceType()) && (r.getTotalNum().compareTo(r.getRecheckQuantity()) == 0)
                    ).collect(Collectors.toList());
            if(IterUtil.isNotEmpty(qimenDeliverieList)){
                throw new ServiceException("面单号不存在不能出库！");
            }
            throw new ServiceException("存在缺货不能出库或面单号为空！");
        }
    }

    private void accountBalance(int size,String branchCode) {
        if(size != 0){
            ResultDO resultDO = arpaCloudUtil.changAccount(branchCode);
            //不是充值用户才判断余额
            if(!resultDO.getMsg().equalsIgnoreCase("不是充值用户无需扣费")){
                if(resultDO.getStatus() != 0){
                    throw new ServiceException(resultDO.getMsg());
                }
                Map<String,Object> data = (Map<String, Object>) resultDO.getData();
                int surplus = Integer.valueOf(String.valueOf(data.get("surplus")));
                if(surplus == 0){
                    throw new ServiceException("客户余额不足，请联系管理人员充值！");
                }else if(size > surplus){
                    throw new ServiceException("客户余额不足，请联系管理人员充值！");
                }
            }
        }
    }
    /*************************************订单、出库单一对一逻辑END************************************************/

    public static List<Records> getObject(String pojo, List<Records> tclass) {
        try {
            return JSONObject.parseObject(pojo, (Type) tclass);
        } catch (Exception e) {
            log.error(tclass + "转 JSON 失败");
        }
        return null;
    }

    /**
     * 异步批量出库确认
     * @author  rfwang
     * @date 2022/5/13 15:59
     * @param outbounds
     * @return com.arpa.core.web.Result
     */
    @Async
    public void batchOutboundConfirm(List<Outbound> outbounds){
        if (IterUtil.isEmpty(outbounds)) {
            return;
        }
        String groupCode = outbounds.get(NUM_ZERO).getGroupCode();
        for (Outbound outbound : outbounds) {
            try {
                iOutboundService.outCheckAndHandelCancelDelivery(outbound);
            } catch (Exception e) {
                log.error("出库确认失败，{}",e.getMessage());
            }
        }
        List<String> outboundCodes = outbounds.stream().map(Outbound::getCode).collect(Collectors.toList());
        //更新出库单为任务完成
        iOutboundService.update(new LambdaUpdateWrapper<Outbound>()
                .in(Outbound::getCode,outboundCodes)
                .eq(Outbound::getGroupCode, groupCode)
                .set(Outbound::getTaskStatus, CommonConst.STR_THREE)
        );
    }



}
